use std::borrow::Cow;
use std::error::Error as StdError;
use std::fmt;
use std::future::Future;
use std::marker::PhantomData;
use bytes::BytesMut;
use serde::Serialize;
use thiserror::Error;
use crate::codec::{Codec, CodecError};
use crate::{
CallerName, FixedName, HeaderMap, HeadersContract, MessageHeaders, NameTemplate, NoHeaders,
OutgoingDestination, OutgoingMessage, PayloadForm, SerializeHeadersError, WithHeaders,
};
use super::sink::{CallCodec, PublishCodec, PublishSink};
#[must_use = "a publish builder does nothing until publish() is awaited"]
pub struct PublishBuilder<Sink: PublishSink, Body, Enc, Hdrs, Dest> {
sink: Sink,
body: Body,
codec: Enc,
headers: Hdrs,
dest: Dest,
options: Option<Sink::Options>,
}
impl<Sink: PublishSink, Body, Enc, Hdrs, Dest> fmt::Debug
for PublishBuilder<Sink, Body, Enc, Hdrs, Dest>
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PublishBuilder").finish_non_exhaustive()
}
}
#[derive(Debug, Clone, Copy)]
pub struct MessageBody<'a, T>(&'a T);
#[derive(Debug, Clone, Copy)]
pub struct RawBody<'a>(&'a [u8]);
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct HeadersUnset;
#[derive(Debug, Clone, Copy)]
pub struct TypedHeaders<'a, H: ?Sized>(&'a H);
#[derive(Debug, Clone, Default)]
pub struct MapHeaders(HeaderMap);
#[derive(Debug, Clone)]
pub struct SuppliedName<'a>(Cow<'a, str>);
#[diagnostic::on_unimplemented(
message = "this publish has no destination yet",
note = "a message type declaring no name takes one at the call: `.to(\"orders.archived\")`; \
one declaring a name template opens its placeholder setters with `.to()`, and the \
publish appears once every placeholder is bound"
)]
pub trait ResolvedName {
fn resolve<'n>(&'n self, declared: &'n str) -> &'n str;
}
impl ResolvedName for FixedName {
fn resolve<'n>(&'n self, declared: &'n str) -> &'n str {
declared
}
}
impl ResolvedName for SuppliedName<'_> {
fn resolve<'n>(&'n self, _declared: &'n str) -> &'n str {
&self.0
}
}
pub trait PublishHeaders {
fn write(self, headers: &mut HeaderMap) -> Result<(), SerializeHeadersError>;
}
impl PublishHeaders for HeadersUnset {
fn write(self, _headers: &mut HeaderMap) -> Result<(), SerializeHeadersError> {
Ok(())
}
}
impl<H: Serialize + ?Sized> PublishHeaders for TypedHeaders<'_, H> {
fn write(self, headers: &mut HeaderMap) -> Result<(), SerializeHeadersError> {
headers.insert_typed(self.0)
}
}
impl PublishHeaders for MapHeaders {
fn write(self, headers: &mut HeaderMap) -> Result<(), SerializeHeadersError> {
headers.overwrite_with(self.0);
Ok(())
}
}
#[diagnostic::on_unimplemented(
message = "the headers of this publish do not match the message's `{C}` contract",
note = "a message declaring `#[outgoing(headers = Meta)]` is published as \
`.message(&value).with_headers(&meta)`; one declaring no contract takes no contract \
value (an arbitrary map still travels through `.with_headers(headers)`)"
)]
pub trait SatisfiesContract<C: HeadersContract> {}
impl SatisfiesContract<NoHeaders> for HeadersUnset {}
impl SatisfiesContract<NoHeaders> for MapHeaders {}
impl<H> SatisfiesContract<WithHeaders<H>> for TypedHeaders<'_, H> {}
#[derive(Debug, Error)]
#[error(transparent)]
pub struct SerializePayloadError(Box<dyn StdError + Send + Sync>);
impl SerializePayloadError {
#[must_use]
pub fn new(source: impl StdError + Send + Sync + 'static) -> Self {
Self(Box::new(source))
}
}
#[doc(hidden)]
pub trait EncodeOutcome {
fn finish(self) -> Result<(), SerializePayloadError>;
}
impl EncodeOutcome for () {
fn finish(self) -> Result<(), SerializePayloadError> {
Ok(())
}
}
impl<E: StdError + Send + Sync + 'static> EncodeOutcome for Result<(), E> {
fn finish(self) -> Result<(), SerializePayloadError> {
self.map_err(SerializePayloadError::new)
}
}
pub trait Serialized {
type Error: StdError + Send + Sync + 'static;
fn wire_bytes(&self, buf: &mut BytesMut) -> Result<WireBytes<'_>, Self::Error>;
}
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub enum WireBytes<'a> {
InBuffer,
Own(&'a [u8]),
}
impl<'a> WireBytes<'a> {
#[must_use]
pub fn of(self, buf: &'a BytesMut) -> &'a [u8] {
match self {
Self::InBuffer => &buf[..],
Self::Own(bytes) => bytes,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct EncodedWire;
#[derive(Debug, Clone, Copy)]
pub struct SerializedWire;
#[diagnostic::on_unimplemented(
message = "`{Self}` does not pick a publish wire",
note = "a typed publish takes a `serde::Serialize` value (the resolved codec encodes it) or \
a `#[derive(Serialized)]` type (its bytes leave as they are)"
)]
pub trait MessageWire {
type Wire;
}
#[diagnostic::do_not_recommend]
impl<T: Serialize> MessageWire for T {
type Wire = EncodedWire;
}
#[doc(hidden)]
#[derive(Debug, Error)]
pub enum PayloadError {
#[error(transparent)]
Codec(#[from] CodecError),
#[error(transparent)]
Serialize(Box<dyn StdError + Send + Sync>),
}
#[diagnostic::on_unimplemented(
message = "no codec is available to encode `{T}` for this publish",
label = "this publish has no codec to encode with",
note = "enable a codec feature on `ruststream` (`json`, `cbor` or `msgpack`), name one for \
this publish with `.with_codec(JsonCodec)`, or give `{T}` its own bytes with \
`#[derive(Serialized)]` so no codec is needed"
)]
#[doc(hidden)]
pub trait WirePayload<T, Enc> {
fn payload<'v, Form: PayloadForm>(
value: &'v T,
codec: &Enc,
buf: &'v mut BytesMut,
) -> Result<Form::Form<'v>, PayloadError>;
}
impl<T: Serialize, Enc: PublishCodec> WirePayload<T, Enc> for EncodedWire {
fn payload<'v, Form: PayloadForm>(
value: &'v T,
codec: &Enc,
buf: &'v mut BytesMut,
) -> Result<Form::Form<'v>, PayloadError> {
Ok(Form::encoded(codec.codec(), value, Form::encode_slot(buf))?)
}
}
impl<T: Serialized, Enc> WirePayload<T, Enc> for SerializedWire {
fn payload<'v, Form: PayloadForm>(
value: &'v T,
_codec: &Enc,
buf: &'v mut BytesMut,
) -> Result<Form::Form<'v>, PayloadError> {
Form::serialized(value, Form::encode_slot(buf))
.map_err(|err| PayloadError::Serialize(Box::new(err)))
}
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum PublishError<E> {
#[error("encoding the message failed: {0}")]
Encode(#[source] CodecError),
#[error("serializing the message's own bytes failed: {0}")]
Serialize(#[source] Box<dyn StdError + Send + Sync>),
#[error("serializing the typed headers failed: {0}")]
Headers(#[source] SerializeHeadersError),
#[error("publishing the message failed: {0}")]
Publish(#[source] E),
}
impl<E> From<PayloadError> for PublishError<E> {
fn from(err: PayloadError) -> Self {
match err {
PayloadError::Codec(err) => Self::Encode(err),
PayloadError::Serialize(err) => Self::Serialize(err),
}
}
}
impl<Sink: PublishSink, Body, Enc, Hdrs, Dest> PublishBuilder<Sink, Body, Enc, Hdrs, Dest> {
pub(crate) const fn new(
sink: Sink,
body: Body,
codec: Enc,
headers: Hdrs,
dest: Dest,
options: Option<Sink::Options>,
) -> Self {
Self {
sink,
body,
codec,
headers,
dest,
options,
}
}
pub const fn options_mut(&mut self) -> &mut Option<Sink::Options> {
&mut self.options
}
}
pub(crate) fn message_of<Sink, T, Enc>(
sink: Sink,
value: &T,
codec: Enc,
) -> PublishBuilder<Sink, MessageBody<'_, T>, Enc, HeadersUnset, T::Form>
where
Sink: PublishSink,
T: OutgoingDestination,
{
PublishBuilder::new(
sink,
MessageBody(value),
codec,
HeadersUnset,
<T::Form as Default>::default(),
None,
)
}
#[cfg(test)]
pub(crate) fn raw_of<Sink, B>(
sink: Sink,
payload: &B,
) -> PublishBuilder<Sink, RawBody<'_>, (), HeadersUnset, CallerName>
where
Sink: PublishSink,
B: AsRef<[u8]> + ?Sized,
{
PublishBuilder::new(
sink,
RawBody(payload.as_ref()),
(),
HeadersUnset,
CallerName,
None,
)
}
impl<'a, Sink: PublishSink, T, Enc, Hdrs, Dest>
PublishBuilder<Sink, MessageBody<'a, T>, Enc, Hdrs, Dest>
{
pub fn with_codec<C: Codec>(
self,
codec: C,
) -> PublishBuilder<Sink, MessageBody<'a, T>, CallCodec<C>, Hdrs, Dest>
where
T: MessageWire<Wire = EncodedWire>,
{
PublishBuilder::new(
self.sink,
self.body,
CallCodec(codec),
self.headers,
self.dest,
self.options,
)
}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot fill the headers of a publish",
note = "pass the message's declared contract by reference (`.with_headers(&meta)`), or an \
already-built header map by value (`.with_headers(headers)`)"
)]
pub trait HeaderSource {
type Position;
fn into_position(self) -> Self::Position;
}
impl<'a, H: ?Sized> HeaderSource for &'a H {
type Position = TypedHeaders<'a, H>;
fn into_position(self) -> Self::Position {
TypedHeaders(self)
}
}
impl HeaderSource for HeaderMap {
type Position = MapHeaders;
fn into_position(self) -> Self::Position {
MapHeaders(self)
}
}
impl<Sink: PublishSink, Body, Enc, Dest> PublishBuilder<Sink, Body, Enc, HeadersUnset, Dest> {
pub fn with_headers<S: HeaderSource>(
self,
headers: S,
) -> PublishBuilder<Sink, Body, Enc, S::Position, Dest> {
PublishBuilder::new(
self.sink,
self.body,
self.codec,
headers.into_position(),
self.dest,
self.options,
)
}
}
impl<Sink: PublishSink, Body, Enc, Hdrs> PublishBuilder<Sink, Body, Enc, Hdrs, CallerName> {
pub fn to<'n>(
self,
name: impl Into<Cow<'n, str>>,
) -> PublishBuilder<Sink, Body, Enc, Hdrs, SuppliedName<'n>> {
PublishBuilder::new(
self.sink,
self.body,
self.codec,
self.headers,
SuppliedName(name.into()),
self.options,
)
}
}
impl<Sink, T, Enc, Hdrs> PublishBuilder<Sink, MessageBody<'_, T>, Enc, Hdrs, NameTemplate>
where
Sink: PublishSink,
T: TemplateAddress<Self>,
{
pub fn to(self) -> T::Builder {
T::begin(self)
}
}
pub trait TemplateAddress<Cont> {
type Builder;
fn begin(cont: Cont) -> Self::Builder;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct MissingSegment<S>(PhantomData<fn() -> S>);
impl<S> MissingSegment<S> {
#[must_use]
pub const fn new() -> Self {
Self(PhantomData)
}
}
#[diagnostic::on_unimplemented(
message = "this address still has an unbound placeholder",
note = "a name template opens one setter per placeholder, and the publish appears once \
every one of them is bound"
)]
pub trait BoundSegment: fmt::Display {}
impl BoundSegment for String {}
pub trait PublishAt {
type Error;
fn publish_at(
self,
name: &str,
) -> impl Future<Output = Result<(), PublishError<Self::Error>>> + Send;
}
async fn deliver<'v, Sink: PublishSink, Hdrs: PublishHeaders>(
mut sink: Sink,
name: &'v str,
payload: <Sink::Payload as PayloadForm>::Form<'v>,
headers: Hdrs,
options: Option<&Sink::Options>,
) -> Result<(), PublishError<Sink::Error>> {
let mut map = sink.base_headers().cloned().unwrap_or_default();
headers.write(&mut map).map_err(PublishError::Headers)?;
let msg = OutgoingMessage::assembled(name, payload, map);
sink.send(msg, options).await.map_err(PublishError::Publish)
}
async fn wire_and_send<Sink, T, Enc, Hdrs>(
sink: Sink,
codec: Enc,
value: &T,
headers: Hdrs,
name: &str,
options: Option<&Sink::Options>,
) -> Result<(), PublishError<Sink::Error>>
where
Sink: PublishSink,
T: MessageWire + Sync,
T::Wire: WirePayload<T, Enc>,
Hdrs: PublishHeaders,
{
let mut buf = BytesMut::new();
let payload =
<T::Wire as WirePayload<T, Enc>>::payload::<Sink::Payload>(value, &codec, &mut buf)?;
deliver(sink, name, payload, headers, options).await
}
impl<Sink, T, Enc, Hdrs, Dest> PublishBuilder<Sink, MessageBody<'_, T>, Enc, Hdrs, Dest>
where
Sink: PublishSink,
T: OutgoingDestination + MessageHeaders + Sync,
Hdrs: PublishHeaders,
{
pub async fn publish(self) -> Result<(), PublishError<Sink::Error>>
where
T: MessageWire,
T::Wire: WirePayload<T, Enc>,
Hdrs: SatisfiesContract<T::Contract>,
Dest: ResolvedName,
{
let Self {
sink,
body,
codec,
headers,
dest,
options,
} = self;
let name = dest.resolve(T::DESTINATION);
wire_and_send(sink, codec, body.0, headers, name, options.as_ref()).await
}
}
impl<Sink, T, Enc, Hdrs> PublishAt
for PublishBuilder<Sink, MessageBody<'_, T>, Enc, Hdrs, NameTemplate>
where
Sink: PublishSink,
T: OutgoingDestination + MessageHeaders + MessageWire + Sync,
T::Wire: WirePayload<T, Enc>,
Enc: Send,
Hdrs: PublishHeaders + SatisfiesContract<T::Contract> + Send,
{
type Error = Sink::Error;
async fn publish_at(self, name: &str) -> Result<(), PublishError<Sink::Error>> {
let Self {
sink,
body,
codec,
headers,
options,
..
} = self;
wire_and_send(sink, codec, body.0, headers, name, options.as_ref()).await
}
}
impl<Sink, Hdrs, Dest> PublishBuilder<Sink, RawBody<'_>, (), Hdrs, Dest>
where
Sink: PublishSink,
Hdrs: PublishHeaders,
{
pub async fn publish(self) -> Result<(), PublishError<Sink::Error>>
where
Dest: ResolvedName,
{
let Self {
sink,
body,
headers,
dest,
options,
..
} = self;
let name = dest.resolve("");
let payload = <Sink::Payload as PayloadForm>::Form::from(body.0);
deliver(sink, name, payload, headers, options.as_ref()).await
}
}