use crate::store::{Store, StoreError, StoreId};
use leptos::prelude::Get;
use std::any::TypeId;
use std::fmt;
use std::sync::{Arc, RwLock};
use std::time::Duration;
use thiserror::Error;
#[derive(Clone, Copy, Debug)]
pub struct CrossInstant {
#[cfg(target_arch = "wasm32")]
millis: f64,
#[cfg(not(target_arch = "wasm32"))]
instant: std::time::Instant,
}
impl CrossInstant {
pub fn now() -> Self {
#[cfg(target_arch = "wasm32")]
{
let millis = js_sys::Date::now();
Self { millis }
}
#[cfg(not(target_arch = "wasm32"))]
{
Self {
instant: std::time::Instant::now(),
}
}
}
pub fn elapsed(&self) -> Duration {
#[cfg(target_arch = "wasm32")]
{
let now = js_sys::Date::now();
let elapsed_ms = now - self.millis;
Duration::from_millis(elapsed_ms.max(0.0) as u64)
}
#[cfg(not(target_arch = "wasm32"))]
{
self.instant.elapsed()
}
}
}
#[derive(Debug, Error, Clone)]
pub enum MiddlewareError {
#[error("Middleware rejected: {0}")]
Rejected(String),
#[error("Validation failed: {0}")]
ValidationFailed(String),
#[error("Middleware timed out after {0}ms")]
Timeout(u64),
#[error("Middleware error: {0}")]
Internal(String),
}
#[derive(Debug, Clone, Default)]
pub enum MiddlewareResult {
#[default]
Continue,
Skip,
Abort(MiddlewareError),
Transform,
}
impl MiddlewareResult {
pub fn should_continue(&self) -> bool {
matches!(self, Self::Continue | Self::Transform)
}
pub fn is_abort(&self) -> bool {
matches!(self, Self::Abort(_))
}
pub fn error(&self) -> Option<&MiddlewareError> {
match self {
Self::Abort(e) => Some(e),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct MutationResult {
pub success: bool,
pub duration: Duration,
pub error: Option<String>,
}
impl MutationResult {
pub fn success(duration: Duration) -> Self {
Self {
success: true,
duration,
error: None,
}
}
pub fn failure(duration: Duration, error: impl Into<String>) -> Self {
Self {
success: false,
duration,
error: Some(error.into()),
}
}
}
#[derive(Debug, Clone)]
pub struct ActionResult {
pub success: bool,
pub duration: Duration,
pub error: Option<String>,
pub output_type: Option<&'static str>,
}
impl ActionResult {
pub fn success(duration: Duration) -> Self {
Self {
success: true,
duration,
error: None,
output_type: None,
}
}
pub fn success_with_output(duration: Duration, output_type: &'static str) -> Self {
Self {
success: true,
duration,
error: None,
output_type: Some(output_type),
}
}
pub fn failure(duration: Duration, error: impl Into<String>) -> Self {
Self {
success: false,
duration,
error: Some(error.into()),
output_type: None,
}
}
}
pub struct MiddlewareContext<'a, S: Store> {
store: &'a S,
mutation_name: &'static str,
timestamp: CrossInstant,
metadata: ContextMetadata,
}
impl<'a, S: Store> MiddlewareContext<'a, S> {
pub fn new(store: &'a S, mutation_name: &'static str) -> Self {
Self {
store,
mutation_name,
timestamp: CrossInstant::now(),
metadata: ContextMetadata::default(),
}
}
pub fn store(&self) -> &S {
self.store
}
pub fn state(&self) -> S::State {
self.store.state().get()
}
pub fn mutation_name(&self) -> &'static str {
self.mutation_name
}
pub fn timestamp(&self) -> CrossInstant {
self.timestamp
}
pub fn elapsed(&self) -> Duration {
self.timestamp.elapsed()
}
pub fn store_id(&self) -> StoreId {
self.store.id()
}
pub fn store_name(&self) -> &'static str {
self.store.name()
}
pub fn metadata_mut(&mut self) -> &mut ContextMetadata {
&mut self.metadata
}
pub fn metadata(&self) -> &ContextMetadata {
&self.metadata
}
}
pub struct ActionContext<'a, S: Store> {
store: &'a S,
action_type: TypeId,
action_name: &'static str,
timestamp: CrossInstant,
metadata: ContextMetadata,
}
impl<'a, S: Store> ActionContext<'a, S> {
pub fn new(store: &'a S, action_type: TypeId, action_name: &'static str) -> Self {
Self {
store,
action_type,
action_name,
timestamp: CrossInstant::now(),
metadata: ContextMetadata::default(),
}
}
pub fn store(&self) -> &S {
self.store
}
pub fn state(&self) -> S::State {
self.store.state().get()
}
pub fn action_type(&self) -> TypeId {
self.action_type
}
pub fn action_name(&self) -> &'static str {
self.action_name
}
pub fn timestamp(&self) -> CrossInstant {
self.timestamp
}
pub fn elapsed(&self) -> Duration {
self.timestamp.elapsed()
}
pub fn store_id(&self) -> StoreId {
self.store.id()
}
pub fn store_name(&self) -> &'static str {
self.store.name()
}
pub fn metadata_mut(&mut self) -> &mut ContextMetadata {
&mut self.metadata
}
pub fn metadata(&self) -> &ContextMetadata {
&self.metadata
}
}
#[derive(Debug, Clone, Default)]
pub struct ContextMetadata {
pub tags: Vec<String>,
pub correlation_id: Option<String>,
pub parent_span_id: Option<String>,
pub custom: std::collections::HashMap<String, String>,
}
impl ContextMetadata {
pub fn new() -> Self {
Self::default()
}
pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
self.tags.push(tag.into());
self
}
pub fn with_correlation_id(mut self, id: impl Into<String>) -> Self {
self.correlation_id = Some(id.into());
self
}
pub fn with_custom(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.custom.insert(key.into(), value.into());
self
}
}
pub trait Middleware<S: Store>: Send + Sync {
fn before_mutate(&self, _ctx: &MiddlewareContext<S>) -> MiddlewareResult {
MiddlewareResult::Continue
}
fn after_mutate(&self, _ctx: &MiddlewareContext<S>, _result: &MutationResult) {}
fn before_action(&self, _ctx: &ActionContext<S>) -> MiddlewareResult {
MiddlewareResult::Continue
}
fn after_action(&self, _ctx: &ActionContext<S>, _result: &ActionResult) {}
fn name(&self) -> &'static str {
std::any::type_name::<Self>()
}
fn priority(&self) -> i32 {
0
}
}
pub struct MiddlewareChain<S: Store> {
middleware: Vec<Arc<dyn Middleware<S>>>,
sorted: bool,
}
impl<S: Store> Default for MiddlewareChain<S> {
fn default() -> Self {
Self::new()
}
}
impl<S: Store> MiddlewareChain<S> {
pub fn new() -> Self {
Self {
middleware: Vec::new(),
sorted: true,
}
}
pub fn add<M: Middleware<S> + 'static>(&mut self, middleware: M) {
self.middleware.push(Arc::new(middleware));
self.sorted = false;
}
pub fn add_arc(&mut self, middleware: Arc<dyn Middleware<S>>) {
self.middleware.push(middleware);
self.sorted = false;
}
pub fn len(&self) -> usize {
self.middleware.len()
}
pub fn is_empty(&self) -> bool {
self.middleware.is_empty()
}
fn ensure_sorted(&mut self) {
if !self.sorted {
self.middleware
.sort_by_key(|b| std::cmp::Reverse(b.priority()));
self.sorted = true;
}
}
pub fn before_mutate(&mut self, ctx: &MiddlewareContext<S>) -> MiddlewareResult {
self.ensure_sorted();
for m in &self.middleware {
let result = m.before_mutate(ctx);
if result.is_abort() {
return result;
}
if matches!(result, MiddlewareResult::Skip) {
break;
}
}
MiddlewareResult::Continue
}
pub fn after_mutate(&mut self, ctx: &MiddlewareContext<S>, result: &MutationResult) {
self.ensure_sorted();
for m in self.middleware.iter().rev() {
m.after_mutate(ctx, result);
}
}
pub fn before_action(&mut self, ctx: &ActionContext<S>) -> MiddlewareResult {
self.ensure_sorted();
for m in &self.middleware {
let result = m.before_action(ctx);
if result.is_abort() {
return result;
}
if matches!(result, MiddlewareResult::Skip) {
break;
}
}
MiddlewareResult::Continue
}
pub fn after_action(&mut self, ctx: &ActionContext<S>, result: &ActionResult) {
self.ensure_sorted();
for m in self.middleware.iter().rev() {
m.after_action(ctx, result);
}
}
}
impl<S: Store> fmt::Debug for MiddlewareChain<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MiddlewareChain")
.field("count", &self.middleware.len())
.field(
"middleware",
&self.middleware.iter().map(|m| m.name()).collect::<Vec<_>>(),
)
.finish()
}
}
#[derive(Debug, Clone)]
pub enum StoreEvent {
StateChanged {
store_id: StoreId,
store_name: &'static str,
timestamp: u64,
},
MutationStarted {
store_id: StoreId,
name: &'static str,
timestamp: u64,
},
MutationCompleted {
store_id: StoreId,
name: &'static str,
duration_ms: u64,
success: bool,
},
ActionDispatched {
store_id: StoreId,
action_type: TypeId,
action_name: &'static str,
timestamp: u64,
},
ActionCompleted {
store_id: StoreId,
action_name: &'static str,
duration_ms: u64,
success: bool,
},
Error {
store_id: StoreId,
message: String,
source: ErrorSource,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorSource {
Mutation,
Action,
Middleware,
Persistence,
Unknown,
}
pub trait EventSubscriber: Send + Sync {
fn on_event(&self, event: &StoreEvent);
fn name(&self) -> &'static str {
std::any::type_name::<Self>()
}
fn filter(&self, _event: &StoreEvent) -> bool {
true
}
}
pub struct EventBus {
subscribers: RwLock<Vec<Arc<dyn EventSubscriber>>>,
}
impl Default for EventBus {
fn default() -> Self {
Self::new()
}
}
impl EventBus {
pub fn new() -> Self {
Self {
subscribers: RwLock::new(Vec::new()),
}
}
pub fn subscribe<S: EventSubscriber + 'static>(&self, subscriber: S) {
if let Ok(mut subs) = self.subscribers.write() {
subs.push(Arc::new(subscriber));
}
}
pub fn subscribe_arc(&self, subscriber: Arc<dyn EventSubscriber>) {
if let Ok(mut subs) = self.subscribers.write() {
subs.push(subscriber);
}
}
pub fn emit(&self, event: StoreEvent) {
if let Ok(subs) = self.subscribers.read() {
for sub in subs.iter() {
if sub.filter(&event) {
sub.on_event(&event);
}
}
}
}
pub fn subscriber_count(&self) -> usize {
self.subscribers.read().map(|s| s.len()).unwrap_or(0)
}
pub fn clear(&self) {
if let Ok(mut subs) = self.subscribers.write() {
subs.clear();
}
}
}
impl fmt::Debug for EventBus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let count = self.subscriber_count();
f.debug_struct("EventBus")
.field("subscriber_count", &count)
.finish()
}
}
pub struct MiddlewareStore<S: Store> {
inner: S,
middleware: Arc<RwLock<MiddlewareChain<S>>>,
event_bus: Arc<EventBus>,
}
impl<S: Store> Clone for MiddlewareStore<S> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
middleware: Arc::clone(&self.middleware),
event_bus: Arc::clone(&self.event_bus),
}
}
}
impl<S: Store> MiddlewareStore<S> {
pub fn new(store: S) -> Self {
Self {
inner: store,
middleware: Arc::new(RwLock::new(MiddlewareChain::new())),
event_bus: Arc::new(EventBus::new()),
}
}
pub fn with_event_bus(store: S, event_bus: Arc<EventBus>) -> Self {
Self {
inner: store,
middleware: Arc::new(RwLock::new(MiddlewareChain::new())),
event_bus,
}
}
pub fn inner(&self) -> &S {
&self.inner
}
pub fn inner_mut(&mut self) -> &mut S {
&mut self.inner
}
pub fn add_middleware<M: Middleware<S> + 'static>(&self, middleware: M) {
if let Ok(mut chain) = self.middleware.write() {
chain.add(middleware);
}
}
pub fn subscribe<E: EventSubscriber + 'static>(&self, subscriber: E) {
self.event_bus.subscribe(subscriber);
}
pub fn event_bus(&self) -> &Arc<EventBus> {
&self.event_bus
}
pub fn mutate<F>(&self, mutation_name: &'static str, mutate_fn: F) -> Result<(), StoreError>
where
F: FnOnce(),
{
let ctx = MiddlewareContext::new(&self.inner, mutation_name);
let start = CrossInstant::now();
self.event_bus.emit(StoreEvent::MutationStarted {
store_id: self.inner.id(),
name: mutation_name,
timestamp: current_timestamp_ms(),
});
let before_result = if let Ok(mut chain) = self.middleware.write() {
chain.before_mutate(&ctx)
} else {
MiddlewareResult::Continue
};
if let MiddlewareResult::Abort(err) = before_result {
let result = MutationResult::failure(start.elapsed(), err.to_string());
if let Ok(mut chain) = self.middleware.write() {
chain.after_mutate(&ctx, &result);
}
self.event_bus.emit(StoreEvent::MutationCompleted {
store_id: self.inner.id(),
name: mutation_name,
duration_ms: start.elapsed().as_millis() as u64,
success: false,
});
return Err(StoreError::MutationFailed(err.to_string()));
}
mutate_fn();
let result = MutationResult::success(start.elapsed());
if let Ok(mut chain) = self.middleware.write() {
chain.after_mutate(&ctx, &result);
}
self.event_bus.emit(StoreEvent::MutationCompleted {
store_id: self.inner.id(),
name: mutation_name,
duration_ms: start.elapsed().as_millis() as u64,
success: true,
});
Ok(())
}
pub fn dispatch<F, R>(
&self,
action_name: &'static str,
action_type: TypeId,
action_fn: F,
) -> Result<R, StoreError>
where
F: FnOnce() -> R,
{
let ctx = ActionContext::new(&self.inner, action_type, action_name);
let start = CrossInstant::now();
self.event_bus.emit(StoreEvent::ActionDispatched {
store_id: self.inner.id(),
action_type,
action_name,
timestamp: current_timestamp_ms(),
});
let before_result = if let Ok(mut chain) = self.middleware.write() {
chain.before_action(&ctx)
} else {
MiddlewareResult::Continue
};
if let MiddlewareResult::Abort(err) = before_result {
let result = ActionResult::failure(start.elapsed(), err.to_string());
if let Ok(mut chain) = self.middleware.write() {
chain.after_action(&ctx, &result);
}
self.event_bus.emit(StoreEvent::ActionCompleted {
store_id: self.inner.id(),
action_name,
duration_ms: start.elapsed().as_millis() as u64,
success: false,
});
return Err(StoreError::MutationFailed(err.to_string()));
}
let output = action_fn();
let result = ActionResult::success_with_output(start.elapsed(), std::any::type_name::<R>());
if let Ok(mut chain) = self.middleware.write() {
chain.after_action(&ctx, &result);
}
self.event_bus.emit(StoreEvent::ActionCompleted {
store_id: self.inner.id(),
action_name,
duration_ms: start.elapsed().as_millis() as u64,
success: true,
});
Ok(output)
}
}
impl<S: Store> Store for MiddlewareStore<S> {
type State = S::State;
fn state(&self) -> leptos::prelude::ReadSignal<Self::State> {
self.inner.state()
}
fn id(&self) -> StoreId {
self.inner.id()
}
fn name(&self) -> &'static str {
self.inner.name()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum LogLevel {
Trace,
Debug,
#[default]
Info,
Warn,
Error,
Off,
}
#[derive(Debug, Clone)]
pub struct LoggingConfig {
pub level: LogLevel,
pub log_state_before: bool,
pub log_state_after: bool,
pub log_timing: bool,
pub prefix: &'static str,
}
impl Default for LoggingConfig {
fn default() -> Self {
Self {
level: LogLevel::Info,
log_state_before: false,
log_state_after: false,
log_timing: true,
prefix: "[Store]",
}
}
}
pub struct LoggingMiddleware {
config: LoggingConfig,
}
impl Default for LoggingMiddleware {
fn default() -> Self {
Self::new()
}
}
impl LoggingMiddleware {
pub fn new() -> Self {
Self {
config: LoggingConfig::default(),
}
}
pub fn with_config(config: LoggingConfig) -> Self {
Self { config }
}
pub fn with_level(mut self, level: LogLevel) -> Self {
self.config.level = level;
self
}
pub fn log_state_before(mut self) -> Self {
self.config.log_state_before = true;
self
}
pub fn log_state_after(mut self) -> Self {
self.config.log_state_after = true;
self
}
pub fn with_prefix(mut self, prefix: &'static str) -> Self {
self.config.prefix = prefix;
self
}
fn should_log(&self) -> bool {
self.config.level != LogLevel::Off
}
fn log(&self, level: LogLevel, message: &str) {
if self.config.level == LogLevel::Off {
return;
}
let should_emit = match (level, self.config.level) {
(LogLevel::Off, _) => false,
(_, LogLevel::Off) => false,
(LogLevel::Error, _) => true,
(LogLevel::Warn, LogLevel::Error) => false,
(LogLevel::Warn, _) => true,
(LogLevel::Info, LogLevel::Error | LogLevel::Warn) => false,
(LogLevel::Info, _) => true,
(LogLevel::Debug, LogLevel::Error | LogLevel::Warn | LogLevel::Info) => false,
(LogLevel::Debug, _) => true,
(LogLevel::Trace, LogLevel::Trace) => true,
(LogLevel::Trace, _) => false,
};
if should_emit {
match level {
LogLevel::Error => leptos::logging::error!("{} {}", self.config.prefix, message),
LogLevel::Warn => leptos::logging::warn!("{} {}", self.config.prefix, message),
LogLevel::Debug => {
leptos::logging::debug_warn!("{} {}", self.config.prefix, message)
}
_ => leptos::logging::log!("{} {}", self.config.prefix, message),
}
}
}
}
impl<S: Store> Middleware<S> for LoggingMiddleware {
fn before_mutate(&self, ctx: &MiddlewareContext<S>) -> MiddlewareResult {
if self.should_log() {
self.log(
LogLevel::Info,
&format!("Mutation started: {}", ctx.mutation_name()),
);
if self.config.log_state_before {
self.log(
LogLevel::Debug,
&format!("State before: (store: {})", ctx.store_name()),
);
}
}
MiddlewareResult::Continue
}
fn after_mutate(&self, ctx: &MiddlewareContext<S>, result: &MutationResult) {
if self.should_log() {
let status = if result.success {
"completed"
} else {
"failed"
};
if self.config.log_timing {
self.log(
if result.success {
LogLevel::Info
} else {
LogLevel::Error
},
&format!(
"Mutation {}: {} ({:?})",
status,
ctx.mutation_name(),
result.duration
),
);
} else {
self.log(
if result.success {
LogLevel::Info
} else {
LogLevel::Error
},
&format!("Mutation {}: {}", status, ctx.mutation_name()),
);
}
if !result.success
&& let Some(ref err) = result.error
{
self.log(LogLevel::Error, &format!("Error: {}", err));
}
if self.config.log_state_after {
self.log(
LogLevel::Debug,
&format!("State after: (store: {})", ctx.store_name()),
);
}
}
}
fn before_action(&self, ctx: &ActionContext<S>) -> MiddlewareResult {
if self.should_log() {
self.log(
LogLevel::Info,
&format!("Action dispatched: {}", ctx.action_name()),
);
}
MiddlewareResult::Continue
}
fn after_action(&self, ctx: &ActionContext<S>, result: &ActionResult) {
if self.should_log() {
let status = if result.success {
"completed"
} else {
"failed"
};
if self.config.log_timing {
self.log(
if result.success {
LogLevel::Info
} else {
LogLevel::Error
},
&format!(
"Action {}: {} ({:?})",
status,
ctx.action_name(),
result.duration
),
);
} else {
self.log(
if result.success {
LogLevel::Info
} else {
LogLevel::Error
},
&format!("Action {}: {}", status, ctx.action_name()),
);
}
if !result.success
&& let Some(ref err) = result.error
{
self.log(LogLevel::Error, &format!("Error: {}", err));
}
}
}
fn name(&self) -> &'static str {
"LoggingMiddleware"
}
fn priority(&self) -> i32 {
-100 }
}
pub struct TimingMiddleware {
warn_threshold_ms: u64,
error_threshold_ms: u64,
}
impl Default for TimingMiddleware {
fn default() -> Self {
Self::new()
}
}
impl TimingMiddleware {
pub fn new() -> Self {
Self {
warn_threshold_ms: 100,
error_threshold_ms: 1000,
}
}
pub fn with_warn_threshold(mut self, ms: u64) -> Self {
self.warn_threshold_ms = ms;
self
}
pub fn with_error_threshold(mut self, ms: u64) -> Self {
self.error_threshold_ms = ms;
self
}
}
impl<S: Store> Middleware<S> for TimingMiddleware {
fn after_mutate(&self, ctx: &MiddlewareContext<S>, result: &MutationResult) {
let duration_ms = result.duration.as_millis() as u64;
if duration_ms >= self.error_threshold_ms {
leptos::logging::error!(
"[Timing] Slow mutation: {} took {}ms (threshold: {}ms)",
ctx.mutation_name(),
duration_ms,
self.error_threshold_ms
);
} else if duration_ms >= self.warn_threshold_ms {
leptos::logging::warn!(
"[Timing] Mutation {} took {}ms",
ctx.mutation_name(),
duration_ms
);
}
}
fn after_action(&self, ctx: &ActionContext<S>, result: &ActionResult) {
let duration_ms = result.duration.as_millis() as u64;
if duration_ms >= self.error_threshold_ms {
leptos::logging::error!(
"[Timing] Slow action: {} took {}ms (threshold: {}ms)",
ctx.action_name(),
duration_ms,
self.error_threshold_ms
);
} else if duration_ms >= self.warn_threshold_ms {
leptos::logging::warn!(
"[Timing] Action {} took {}ms",
ctx.action_name(),
duration_ms
);
}
}
fn name(&self) -> &'static str {
"TimingMiddleware"
}
fn priority(&self) -> i32 {
-50 }
}
pub type ValidationFn<State> = Box<dyn Fn(&State) -> Result<(), String> + Send + Sync>;
pub struct ValidationMiddleware<State> {
validators: Vec<ValidationFn<State>>,
}
impl<State> Default for ValidationMiddleware<State> {
fn default() -> Self {
Self::new()
}
}
impl<State> ValidationMiddleware<State> {
pub fn new() -> Self {
Self {
validators: Vec::new(),
}
}
pub fn add_validator<F>(mut self, validator: F) -> Self
where
F: Fn(&State) -> Result<(), String> + Send + Sync + 'static,
{
self.validators.push(Box::new(validator));
self
}
}
impl<S: Store> Middleware<S> for ValidationMiddleware<S::State> {
fn before_mutate(&self, ctx: &MiddlewareContext<S>) -> MiddlewareResult {
let state = ctx.state();
for validator in &self.validators {
if let Err(err) = validator(&state) {
return MiddlewareResult::Abort(MiddlewareError::ValidationFailed(err));
}
}
MiddlewareResult::Continue
}
fn name(&self) -> &'static str {
"ValidationMiddleware"
}
fn priority(&self) -> i32 {
100 }
}
#[cfg(feature = "tracing")]
pub struct TracingMiddleware {
service_name: &'static str,
}
#[cfg(feature = "tracing")]
impl Default for TracingMiddleware {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "tracing")]
impl TracingMiddleware {
pub fn new() -> Self {
Self {
service_name: "leptos-store",
}
}
pub fn with_service_name(mut self, name: &'static str) -> Self {
self.service_name = name;
self
}
}
#[cfg(feature = "tracing")]
impl<S: Store> Middleware<S> for TracingMiddleware {
fn before_mutate(&self, ctx: &MiddlewareContext<S>) -> MiddlewareResult {
tracing::info_span!(
"store.mutation",
store = ctx.store_name(),
mutation = ctx.mutation_name(),
service = self.service_name,
);
MiddlewareResult::Continue
}
fn before_action(&self, ctx: &ActionContext<S>) -> MiddlewareResult {
tracing::info_span!(
"store.action",
store = ctx.store_name(),
action = ctx.action_name(),
service = self.service_name,
);
MiddlewareResult::Continue
}
fn name(&self) -> &'static str {
"TracingMiddleware"
}
fn priority(&self) -> i32 {
200 }
}
fn current_timestamp_ms() -> u64 {
#[cfg(target_arch = "wasm32")]
{
js_sys::Date::now() as u64
}
#[cfg(not(target_arch = "wasm32"))]
{
use std::time::SystemTime;
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0)
}
}
#[cfg(test)]
pub fn test_middleware_context<S: Store>(store: &S) -> MiddlewareContext<'_, S> {
MiddlewareContext::new(store, "test_mutation")
}
#[cfg(test)]
pub fn test_action_context<S: Store>(store: &S) -> ActionContext<'_, S> {
ActionContext::new(store, TypeId::of::<()>(), "test_action")
}
#[cfg(test)]
mod tests {
use super::*;
use leptos::prelude::*;
use std::sync::atomic::{AtomicU32, Ordering};
#[derive(Clone, Debug, Default)]
#[allow(dead_code)]
struct TestState {
count: i32,
}
#[derive(Clone)]
struct TestStore {
state: RwSignal<TestState>,
}
impl TestStore {
fn new() -> Self {
Self {
state: RwSignal::new(TestState::default()),
}
}
}
impl Store for TestStore {
type State = TestState;
fn state(&self) -> ReadSignal<Self::State> {
self.state.read_only()
}
}
struct CountingMiddleware {
before_mutate_count: AtomicU32,
after_mutate_count: AtomicU32,
}
impl CountingMiddleware {
fn new() -> Self {
Self {
before_mutate_count: AtomicU32::new(0),
after_mutate_count: AtomicU32::new(0),
}
}
fn before_count(&self) -> u32 {
self.before_mutate_count.load(Ordering::SeqCst)
}
fn after_count(&self) -> u32 {
self.after_mutate_count.load(Ordering::SeqCst)
}
}
impl<S: Store> Middleware<S> for CountingMiddleware {
fn before_mutate(&self, _ctx: &MiddlewareContext<S>) -> MiddlewareResult {
self.before_mutate_count.fetch_add(1, Ordering::SeqCst);
MiddlewareResult::Continue
}
fn after_mutate(&self, _ctx: &MiddlewareContext<S>, _result: &MutationResult) {
self.after_mutate_count.fetch_add(1, Ordering::SeqCst);
}
}
struct AbortingMiddleware;
impl<S: Store> Middleware<S> for AbortingMiddleware {
fn before_mutate(&self, _ctx: &MiddlewareContext<S>) -> MiddlewareResult {
MiddlewareResult::Abort(MiddlewareError::Rejected("Test abort".to_string()))
}
}
#[test]
fn test_middleware_result_methods() {
assert!(MiddlewareResult::Continue.should_continue());
assert!(MiddlewareResult::Transform.should_continue());
assert!(!MiddlewareResult::Skip.should_continue());
assert!(
!MiddlewareResult::Abort(MiddlewareError::Rejected("".to_string())).should_continue()
);
assert!(!MiddlewareResult::Continue.is_abort());
assert!(MiddlewareResult::Abort(MiddlewareError::Rejected("".to_string())).is_abort());
let abort = MiddlewareResult::Abort(MiddlewareError::Rejected("test".to_string()));
assert!(abort.error().is_some());
assert!(MiddlewareResult::Continue.error().is_none());
}
#[test]
fn test_mutation_result() {
let success = MutationResult::success(Duration::from_millis(10));
assert!(success.success);
assert!(success.error.is_none());
let failure = MutationResult::failure(Duration::from_millis(5), "test error");
assert!(!failure.success);
assert_eq!(failure.error, Some("test error".to_string()));
}
#[test]
fn test_action_result() {
let success = ActionResult::success(Duration::from_millis(10));
assert!(success.success);
assert!(success.error.is_none());
let success_with_output =
ActionResult::success_with_output(Duration::from_millis(10), "String");
assert!(success_with_output.success);
assert_eq!(success_with_output.output_type, Some("String"));
let failure = ActionResult::failure(Duration::from_millis(5), "action error");
assert!(!failure.success);
assert_eq!(failure.error, Some("action error".to_string()));
}
#[test]
fn test_context_metadata() {
let meta = ContextMetadata::new()
.with_tag("test")
.with_correlation_id("abc-123")
.with_custom("key", "value");
assert_eq!(meta.tags, vec!["test"]);
assert_eq!(meta.correlation_id, Some("abc-123".to_string()));
assert_eq!(meta.custom.get("key"), Some(&"value".to_string()));
}
#[test]
fn test_middleware_chain_add_and_len() {
let mut chain: MiddlewareChain<TestStore> = MiddlewareChain::new();
assert!(chain.is_empty());
assert_eq!(chain.len(), 0);
chain.add(CountingMiddleware::new());
assert!(!chain.is_empty());
assert_eq!(chain.len(), 1);
}
#[test]
fn test_middleware_chain_execution() {
let store = TestStore::new();
let counting = Arc::new(CountingMiddleware::new());
let mut chain: MiddlewareChain<TestStore> = MiddlewareChain::new();
chain.add_arc(counting.clone());
let ctx = MiddlewareContext::new(&store, "test");
let result = chain.before_mutate(&ctx);
assert!(result.should_continue());
assert_eq!(counting.before_count(), 1);
chain.after_mutate(&ctx, &MutationResult::success(Duration::from_millis(1)));
assert_eq!(counting.after_count(), 1);
}
#[test]
fn test_middleware_chain_abort() {
let store = TestStore::new();
let mut chain: MiddlewareChain<TestStore> = MiddlewareChain::new();
chain.add(AbortingMiddleware);
let ctx = MiddlewareContext::new(&store, "test");
let result = chain.before_mutate(&ctx);
assert!(result.is_abort());
}
#[test]
fn test_event_bus() {
struct TestSubscriber {
count: AtomicU32,
}
impl EventSubscriber for TestSubscriber {
fn on_event(&self, _event: &StoreEvent) {
self.count.fetch_add(1, Ordering::SeqCst);
}
}
let bus = EventBus::new();
assert_eq!(bus.subscriber_count(), 0);
let subscriber = Arc::new(TestSubscriber {
count: AtomicU32::new(0),
});
bus.subscribe_arc(subscriber.clone());
assert_eq!(bus.subscriber_count(), 1);
bus.emit(StoreEvent::StateChanged {
store_id: StoreId::new::<TestStore>(),
store_name: "TestStore",
timestamp: 12345,
});
assert_eq!(subscriber.count.load(Ordering::SeqCst), 1);
bus.clear();
assert_eq!(bus.subscriber_count(), 0);
}
#[test]
fn test_event_subscriber_filter() {
struct FilteredSubscriber {
mutation_count: AtomicU32,
}
impl EventSubscriber for FilteredSubscriber {
fn on_event(&self, _event: &StoreEvent) {
self.mutation_count.fetch_add(1, Ordering::SeqCst);
}
fn filter(&self, event: &StoreEvent) -> bool {
matches!(event, StoreEvent::MutationCompleted { .. })
}
}
let bus = EventBus::new();
let subscriber = Arc::new(FilteredSubscriber {
mutation_count: AtomicU32::new(0),
});
bus.subscribe_arc(subscriber.clone());
bus.emit(StoreEvent::StateChanged {
store_id: StoreId::new::<TestStore>(),
store_name: "TestStore",
timestamp: 12345,
});
assert_eq!(subscriber.mutation_count.load(Ordering::SeqCst), 0);
bus.emit(StoreEvent::MutationCompleted {
store_id: StoreId::new::<TestStore>(),
name: "test",
duration_ms: 10,
success: true,
});
assert_eq!(subscriber.mutation_count.load(Ordering::SeqCst), 1);
}
#[test]
fn test_middleware_store() {
let store = TestStore::new();
let mw_store = MiddlewareStore::new(store);
let _state = mw_store.state();
let _id = mw_store.id();
let _name = mw_store.name();
}
#[test]
fn test_middleware_error_display() {
assert_eq!(
MiddlewareError::Rejected("test".to_string()).to_string(),
"Middleware rejected: test"
);
assert_eq!(
MiddlewareError::ValidationFailed("invalid".to_string()).to_string(),
"Validation failed: invalid"
);
assert_eq!(
MiddlewareError::Timeout(1000).to_string(),
"Middleware timed out after 1000ms"
);
assert_eq!(
MiddlewareError::Internal("oops".to_string()).to_string(),
"Middleware error: oops"
);
}
}