use std::convert::Infallible;
use fred::clients::Pool;
use ruststream::runtime::{Context, Ctx, FromContext};
use ruststream::{BuildBatchContext, BuildContext, Field};
use crate::list::RedisListMessage;
use crate::message::RedisMessage;
use crate::pipeline::{Form, RedisPipeline, RoundMessage};
use crate::pubsub::RedisPubSubMessage;
use crate::seek::{EntryId, RedisGroupPosition, RedisGroupSeeker};
#[derive(Debug, Clone)]
pub struct StreamContext {
entry: EntryId,
position: RedisGroupPosition,
seeker: RedisGroupSeeker,
}
impl StreamContext {
#[must_use]
pub const fn entry_id(&self) -> EntryId {
self.entry
}
#[must_use]
pub const fn position(&self) -> RedisGroupPosition {
self.position
}
#[must_use]
pub fn consumer_group(&self) -> &str {
self.seeker.group()
}
#[must_use]
pub const fn seeker(&self) -> &RedisGroupSeeker {
&self.seeker
}
#[must_use]
pub const fn pool(&self) -> &Pool {
self.seeker.pool()
}
}
impl BuildContext<RedisMessage> for StreamContext {
fn build(msg: &RedisMessage) -> Self {
Self {
entry: msg.entry_id(),
position: ruststream::Positioned::position(msg),
seeker: msg.seeker().clone(),
}
}
}
#[derive(Debug, Clone)]
pub struct StreamBatchContext {
seeker: RedisGroupSeeker,
}
impl StreamBatchContext {
#[must_use]
pub fn consumer_group(&self) -> &str {
self.seeker.group()
}
#[must_use]
pub const fn seeker(&self) -> &RedisGroupSeeker {
&self.seeker
}
#[must_use]
pub const fn pool(&self) -> &Pool {
self.seeker.pool()
}
}
impl BuildBatchContext<RedisMessage> for StreamBatchContext {
fn build(first: &RedisMessage) -> Self {
Self {
seeker: first.seeker().clone(),
}
}
}
#[derive(Debug, Clone)]
pub struct PubSubContext {
channel: String,
from_pattern: bool,
pool: Pool,
}
impl PubSubContext {
#[must_use]
pub fn new(channel: impl Into<String>, from_pattern: bool, pool: Pool) -> Self {
Self {
channel: channel.into(),
from_pattern,
pool,
}
}
#[must_use]
pub const fn pool(&self) -> &Pool {
&self.pool
}
#[must_use]
pub fn channel(&self) -> &str {
&self.channel
}
#[must_use]
pub const fn from_pattern(&self) -> bool {
self.from_pattern
}
}
impl BuildContext<RedisPubSubMessage> for PubSubContext {
fn build(msg: &RedisPubSubMessage) -> Self {
Self {
channel: msg.channel().to_owned(),
from_pattern: msg.from_pattern(),
pool: msg.pool().clone(),
}
}
}
#[derive(Debug, Clone)]
pub struct PoolContext {
pool: Pool,
}
impl PoolContext {
#[must_use]
pub const fn pool(&self) -> &Pool {
&self.pool
}
pub(crate) const fn from_pool(pool: Pool) -> Self {
Self { pool }
}
}
impl BuildContext<RedisMessage> for PoolContext {
fn build(msg: &RedisMessage) -> Self {
Self::from_pool(msg.seeker().pool().clone())
}
}
impl BuildContext<RedisListMessage> for PoolContext {
fn build(msg: &RedisListMessage) -> Self {
Self::from_pool(msg.pool().clone())
}
}
impl BuildContext<RedisPubSubMessage> for PoolContext {
fn build(msg: &RedisPubSubMessage) -> Self {
Self::from_pool(msg.pool().clone())
}
}
#[derive(Debug, Clone)]
pub struct PipelineContext {
pipeline: RedisPipeline,
pool: Pool,
}
impl PipelineContext {
#[must_use]
pub const fn pipeline(&self) -> &RedisPipeline {
&self.pipeline
}
#[must_use]
pub const fn pool(&self) -> &Pool {
&self.pool
}
}
impl<F: Form> BuildContext<RoundMessage<F>> for PipelineContext {
fn build(msg: &RoundMessage<F>) -> Self {
Self {
pipeline: RedisPipeline::new(msg.round().clone()),
pool: F::pool(msg.inner()).clone(),
}
}
}
impl<F: Form> BuildBatchContext<RoundMessage<F>> for PipelineContext {
fn build(first: &RoundMessage<F>) -> Self {
<Self as BuildContext<RoundMessage<F>>>::build(first)
}
}
impl<F: Form> BuildContext<RoundMessage<F>> for PoolContext {
fn build(msg: &RoundMessage<F>) -> Self {
Self::from_pool(F::pool(msg.inner()).clone())
}
}
impl<State: Sync> FromContext<PipelineContext, State> for Ctx<keys::FredPool> {
type Rejection = Infallible;
fn from_context(
ctx: &mut Context<'_, PipelineContext, State>,
) -> impl Future<Output = Result<Self, Infallible>> + Send {
let pool = ctx.context(keys::FredPool).clone();
async move { Ok(Self(pool)) }
}
}
impl<State: Sync> FromContext<StreamContext, State> for Ctx<keys::FredPool> {
type Rejection = Infallible;
fn from_context(
ctx: &mut Context<'_, StreamContext, State>,
) -> impl Future<Output = Result<Self, Infallible>> + Send {
let pool = ctx.context(keys::FredPool).clone();
async move { Ok(Self(pool)) }
}
}
impl<State: Sync> FromContext<PubSubContext, State> for Ctx<keys::FredPool> {
type Rejection = Infallible;
fn from_context(
ctx: &mut Context<'_, PubSubContext, State>,
) -> impl Future<Output = Result<Self, Infallible>> + Send {
let pool = ctx.context(keys::FredPool).clone();
async move { Ok(Self(pool)) }
}
}
pub mod keys {
use ruststream::ContextField;
use fred::clients::Pool;
use super::{
Field, PipelineContext, PoolContext, PubSubContext, RedisGroupPosition, RedisGroupSeeker,
RedisPipeline, StreamBatchContext, StreamContext,
};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct Pipeline;
impl ContextField for Pipeline {
type Context = PipelineContext;
type Value = RedisPipeline;
fn read(self, src: &PipelineContext) -> RedisPipeline {
src.pipeline().clone()
}
}
impl Field<PipelineContext> for Pipeline {
type Value<'a> = &'a RedisPipeline;
fn get(self, src: &PipelineContext) -> &RedisPipeline {
src.pipeline()
}
}
impl Field<PipelineContext> for FredPool {
type Value<'a> = &'a Pool;
fn get(self, src: &PipelineContext) -> &Pool {
src.pool()
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct FredPool;
impl ContextField for FredPool {
type Context = PoolContext;
type Value = Pool;
fn read(self, src: &PoolContext) -> Pool {
src.pool().clone()
}
}
impl Field<PoolContext> for FredPool {
type Value<'a> = &'a Pool;
fn get(self, src: &PoolContext) -> &Pool {
src.pool()
}
}
impl Field<StreamContext> for FredPool {
type Value<'a> = &'a Pool;
fn get(self, src: &StreamContext) -> &Pool {
src.pool()
}
}
impl Field<StreamBatchContext> for FredPool {
type Value<'a> = &'a Pool;
fn get(self, src: &StreamBatchContext) -> &Pool {
src.pool()
}
}
impl Field<PubSubContext> for FredPool {
type Value<'a> = &'a Pool;
fn get(self, src: &PubSubContext) -> &Pool {
src.pool()
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct EntryId;
impl Field<StreamContext> for EntryId {
type Value<'a> = crate::EntryId;
fn get(self, src: &StreamContext) -> crate::EntryId {
src.entry_id()
}
}
impl ContextField for EntryId {
type Context = StreamContext;
type Value = crate::EntryId;
fn read(self, src: &StreamContext) -> crate::EntryId {
src.entry_id()
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct Position;
impl Field<StreamContext> for Position {
type Value<'a> = RedisGroupPosition;
fn get(self, src: &StreamContext) -> RedisGroupPosition {
src.position()
}
}
impl ContextField for Position {
type Context = StreamContext;
type Value = RedisGroupPosition;
fn read(self, src: &StreamContext) -> RedisGroupPosition {
src.position()
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct SeekHandle;
impl Field<StreamContext> for SeekHandle {
type Value<'a> = &'a RedisGroupSeeker;
fn get(self, src: &StreamContext) -> &RedisGroupSeeker {
src.seeker()
}
}
impl Field<StreamBatchContext> for SeekHandle {
type Value<'a> = &'a RedisGroupSeeker;
fn get(self, src: &StreamBatchContext) -> &RedisGroupSeeker {
src.seeker()
}
}
impl ContextField for SeekHandle {
type Context = StreamContext;
type Value = RedisGroupSeeker;
fn read(self, src: &StreamContext) -> RedisGroupSeeker {
src.seeker().clone()
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct ConsumerGroup;
impl Field<StreamContext> for ConsumerGroup {
type Value<'a> = &'a str;
fn get(self, src: &StreamContext) -> &str {
src.consumer_group()
}
}
impl Field<StreamBatchContext> for ConsumerGroup {
type Value<'a> = &'a str;
fn get(self, src: &StreamBatchContext) -> &str {
src.consumer_group()
}
}
impl ContextField for ConsumerGroup {
type Context = StreamContext;
type Value = String;
fn read(self, src: &StreamContext) -> String {
src.consumer_group().to_owned()
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct Channel;
impl Field<PubSubContext> for Channel {
type Value<'a> = &'a str;
fn get(self, src: &PubSubContext) -> &str {
src.channel()
}
}
impl ContextField for Channel {
type Context = PubSubContext;
type Value = String;
fn read(self, src: &PubSubContext) -> String {
src.channel().to_owned()
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct FromPattern;
impl Field<PubSubContext> for FromPattern {
type Value<'a> = bool;
fn get(self, src: &PubSubContext) -> bool {
src.from_pattern()
}
}
impl ContextField for FromPattern {
type Context = PubSubContext;
type Value = bool;
fn read(self, src: &PubSubContext) -> bool {
src.from_pattern()
}
}
}
#[cfg(test)]
mod tests {
use super::PubSubContext;
use super::keys::{Channel, FromPattern};
use fred::clients::Pool;
use fred::types::config::Config;
use ruststream::{ContextField, Field};
fn offline_pool() -> Pool {
Pool::new(Config::default(), None, None, None, 1).expect("offline pool")
}
#[test]
fn pubsub_keys_read_channel_and_pattern_flag() {
let exact = PubSubContext::new("events", false, offline_pool());
assert_eq!(Channel.get(&exact), "events");
assert!(!FromPattern.get(&exact));
let matched = PubSubContext::new("events.user", true, offline_pool());
assert_eq!(Channel.get(&matched), "events.user");
assert!(FromPattern.get(&matched));
}
#[test]
fn pubsub_context_field_keys_yield_owned_values() {
let pubsub = PubSubContext::new("orders.eu", true, offline_pool());
assert_eq!(
<Channel as ContextField>::read(Channel, &pubsub),
"orders.eu".to_owned()
);
assert!(<FromPattern as ContextField>::read(FromPattern, &pubsub));
}
}