use std::any::type_name;
use std::{error::Error as StdError, future::Future, num::NonZeroUsize, time::Duration};
use futures::Stream;
#[cfg(feature = "asyncapi")]
use crate::asyncapi::Bindings;
use crate::subscription::copy_path::Sealed as CopyPathDeclares;
use crate::{
Broker, ConnectedBroker, CopyPath, DeclareRetryError, HeaderMap, IncomingMessage,
OutgoingMessage, Publisher, RetryDeclaration, Subscriber,
};
#[diagnostic::on_unimplemented(
message = "`{Self}` does not deliver messages in batches",
label = "this subscription has no batching of its own",
note = "a handler taking `&[T]` is handed whole batches: mount it on a subscription kind \
that batches. A broker whose transport has no native batches gives its subscriber \
this capability through the `Buffered` adapter (see the broker-authors guide)"
)]
pub trait BatchSubscriber: Subscriber {
type Batch: IntoIterator<Item = <Self as Subscriber>::Message> + Send;
fn batches(
&mut self,
size: NonZeroUsize,
) -> impl Stream<Item = Result<Self::Batch, <Self as Subscriber>::Error>> + Send + '_;
}
pub trait Seekable: Subscriber {
type Seeker: Seeker;
fn seeker(&self) -> Self::Seeker;
}
pub trait Seeker: Clone + Send + Sync + 'static {
type Position: Send;
type Error: StdError + Send + Sync + 'static;
fn seek(&self, to: Self::Position) -> impl Future<Output = Result<(), Self::Error>> + Send;
}
pub trait Positioned: IncomingMessage {
type Position: Send;
fn position(&self) -> Self::Position;
}
#[diagnostic::on_unimplemented(
message = "`{Self}` does not support broker-side transactions",
note = "for an `Out<impl TransactionalPublisher, _>` slot, attach a policy whose live \
publisher is transactional (a transactional producer configuration)"
)]
pub trait TransactionalPublisher: Publisher {
fn begin_transaction(&self) -> impl Future<Output = Result<(), Self::Error>> + Send;
fn commit(&self) -> impl Future<Output = Result<(), Self::Error>> + Send;
fn abort(&self) -> impl Future<Output = Result<(), Self::Error>> + Send;
}
#[must_use = "a transaction does nothing until settled with commit() or abort()"]
pub trait Transaction: Send {
type Error: StdError + Send + Sync + 'static;
type Options: Clone + Send + Sync + 'static;
fn publish(
&mut self,
msg: OutgoingMessage<'_>,
options: Option<&Self::Options>,
) -> impl Future<Output = Result<(), Self::Error>> + Send;
fn commit(self) -> impl Future<Output = Result<(), Self::Error>> + Send;
fn abort(self) -> impl Future<Output = Result<(), Self::Error>> + Send;
fn base_headers(&self) -> Option<&HeaderMap> {
None
}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` does not open caller-owned transactions",
note = "for an `Out<impl OwnedTransactions, _>` slot, attach a policy whose live publisher \
buffers client-side transactions; Kafka-like brokers offer only the borrowed \
`TransactionalPublisher` kind"
)]
pub trait OwnedTransactions: Publisher {
type Transaction: Transaction;
fn transaction(&self) -> impl Future<Output = Result<Self::Transaction, Self::Error>> + Send;
}
#[diagnostic::on_unimplemented(
message = "`{Self}` does not support request / reply messaging",
note = "for an `Out<impl RequestReply, _>` slot, attach a policy whose live publisher \
correlates replies natively (NATS-style); Kafka and classic queues do not"
)]
pub trait RequestReply: Publisher {
type Reply: IncomingMessage;
fn request(
&self,
msg: OutgoingMessage<'_>,
timeout: Duration,
) -> impl Future<Output = Result<Self::Reply, Self::Error>> + Send;
}
pub trait Partitioned {
fn partition_key(&self) -> Option<&[u8]>;
}
pub trait Subscribe: ConnectedBroker {
type Subscriber: Subscriber;
type Copies: CopyPath;
fn subscribe(
&self,
name: &str,
) -> impl Future<Output = Result<Self::Subscriber, Self::Error>> + Send;
fn declare_retry(
&self,
name: &str,
declaration: &RetryDeclaration,
) -> Result<(), DeclareRetryError> {
let _ = name;
if declaration.declares_nothing() {
return Ok(());
}
<Self::Copies as CopyPathDeclares>::declared_by_name(type_name::<Self>())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct ServerSpec {
pub host: Option<String>,
pub protocol: String,
pub protocol_version: Option<String>,
pub description: Option<String>,
pub security: Vec<SecurityScheme>,
#[cfg(feature = "asyncapi")]
pub bindings: Bindings,
}
impl ServerSpec {
#[must_use]
pub fn new(host: impl Into<String>, protocol: impl Into<String>) -> Self {
Self {
host: Some(host.into()),
protocol: protocol.into(),
protocol_version: None,
description: None,
security: Vec::new(),
#[cfg(feature = "asyncapi")]
bindings: Bindings::new(),
}
}
#[must_use]
pub fn from_url(url: &str, protocol: impl Into<String>) -> Self {
Self::new(Self::host_from_url(url), protocol)
}
#[must_use]
pub fn host_from_url(url: &str) -> String {
let after_scheme = url.split_once("://").map_or(url, |(_, rest)| rest);
let authority = after_scheme
.split_once(['/', '?', '#'])
.map_or(after_scheme, |(authority, _)| authority);
authority
.rsplit_once('@')
.map_or(authority, |(_, host)| host)
.to_owned()
}
#[must_use]
pub fn in_process(protocol: impl Into<String>) -> Self {
Self {
host: None,
protocol: protocol.into(),
protocol_version: None,
description: None,
security: Vec::new(),
#[cfg(feature = "asyncapi")]
bindings: Bindings::new(),
}
}
#[must_use]
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
#[must_use]
pub fn protocol_version(mut self, version: impl Into<String>) -> Self {
self.protocol_version = Some(version.into());
self
}
#[must_use]
pub fn security(mut self, scheme: SecurityScheme) -> Self {
self.security.push(scheme);
self
}
#[cfg(feature = "asyncapi")]
#[must_use]
pub fn bindings(mut self, bindings: Bindings) -> Self {
self.bindings = bindings;
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SecurityScheme {
pub(crate) kind: SecuritySchemeKind,
pub(crate) description: Option<String>,
}
#[cfg_attr(not(feature = "asyncapi"), allow(dead_code))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum SecuritySchemeKind {
UserPassword,
ApiKey {
location: ApiKeyLocation,
},
X509,
Plain,
ScramSha256,
ScramSha512,
Gssapi,
Http {
scheme: String,
},
HttpApiKey {
name: String,
location: HttpApiKeyLocation,
},
OpenIdConnect {
url: String,
},
Oauth2 {
flows: String,
},
Custom {
object: String,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ApiKeyLocation {
User,
Password,
}
impl ApiKeyLocation {
#[cfg_attr(not(feature = "asyncapi"), allow(dead_code))]
pub(crate) fn as_api(self) -> &'static str {
match self {
Self::User => "user",
Self::Password => "password",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HttpApiKeyLocation {
Query,
Header,
Cookie,
}
impl HttpApiKeyLocation {
#[cfg_attr(not(feature = "asyncapi"), allow(dead_code))]
pub(crate) fn as_api(self) -> &'static str {
match self {
Self::Query => "query",
Self::Header => "header",
Self::Cookie => "cookie",
}
}
}
impl SecurityScheme {
fn of(kind: SecuritySchemeKind) -> Self {
Self {
kind,
description: None,
}
}
#[must_use]
pub fn user_password() -> Self {
Self::of(SecuritySchemeKind::UserPassword)
}
#[must_use]
pub fn api_key(location: ApiKeyLocation) -> Self {
Self::of(SecuritySchemeKind::ApiKey { location })
}
#[must_use]
pub fn x509() -> Self {
Self::of(SecuritySchemeKind::X509)
}
#[must_use]
pub fn plain() -> Self {
Self::of(SecuritySchemeKind::Plain)
}
#[must_use]
pub fn scram_sha256() -> Self {
Self::of(SecuritySchemeKind::ScramSha256)
}
#[must_use]
pub fn scram_sha512() -> Self {
Self::of(SecuritySchemeKind::ScramSha512)
}
#[must_use]
pub fn gssapi() -> Self {
Self::of(SecuritySchemeKind::Gssapi)
}
#[must_use]
pub fn http(scheme: impl Into<String>) -> Self {
Self::of(SecuritySchemeKind::Http {
scheme: scheme.into(),
})
}
#[must_use]
pub fn http_api_key(name: impl Into<String>, location: HttpApiKeyLocation) -> Self {
Self::of(SecuritySchemeKind::HttpApiKey {
name: name.into(),
location,
})
}
#[must_use]
pub fn open_id_connect(url: impl Into<String>) -> Self {
Self::of(SecuritySchemeKind::OpenIdConnect { url: url.into() })
}
#[cfg(feature = "json")]
#[must_use]
#[allow(clippy::needless_pass_by_value)]
pub fn oauth2(flows: serde_json::Value) -> Self {
Self::of(SecuritySchemeKind::Oauth2 {
flows: flows.to_string(),
})
}
#[cfg(feature = "json")]
#[must_use]
#[allow(clippy::needless_pass_by_value)]
pub fn custom(object: serde_json::Value) -> Self {
Self::of(SecuritySchemeKind::Custom {
object: object.to_string(),
})
}
#[must_use]
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
}
pub trait DescribeServer: Broker {
fn describe_server(&self) -> ServerSpec;
}
#[cfg(test)]
mod tests;