Skip to main content

a3s_boot/transport/
mod.rs

1use crate::pipeline::{PipelineComponent, PipelineOverrides};
2use crate::{
3    catch_errors, validate_json_value_with_options, validate_value, BootError, BootErrorKind,
4    BoxFuture, ExecutionContext, ExecutionInterceptor, ExecutionTransportKind, Guard, Result,
5    TransportExceptionFilter, Validate, ValidationOptions, ValidationSchema,
6};
7use serde::de::DeserializeOwned;
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10use std::collections::BTreeMap;
11use std::future::Future;
12use std::ops::Deref;
13use std::sync::Arc;
14
15#[cfg(feature = "grpc-transport")]
16mod grpc;
17#[cfg(feature = "kafka-transport")]
18mod kafka;
19#[cfg(feature = "mqtt-transport")]
20mod mqtt;
21#[cfg(feature = "nats-transport")]
22mod nats;
23#[cfg(feature = "rabbitmq-transport")]
24mod rabbitmq;
25#[cfg(feature = "redis-transport")]
26mod redis;
27#[cfg(feature = "tcp-transport")]
28mod tcp;
29
30#[cfg(feature = "grpc-transport")]
31pub use self::grpc::{GrpcTransport, GrpcTransportClient, GrpcTransportOptions};
32#[cfg(feature = "kafka-transport")]
33pub use self::kafka::{KafkaTransport, KafkaTransportClient, KafkaTransportOptions};
34#[cfg(feature = "mqtt-transport")]
35pub use self::mqtt::{MqttTransport, MqttTransportClient, MqttTransportOptions, MqttTransportQoS};
36#[cfg(feature = "nats-transport")]
37pub use self::nats::{NatsTransport, NatsTransportClient, NatsTransportOptions};
38#[cfg(feature = "rabbitmq-transport")]
39pub use self::rabbitmq::{RabbitMqTransport, RabbitMqTransportClient, RabbitMqTransportOptions};
40#[cfg(feature = "redis-transport")]
41pub use self::redis::{RedisTransport, RedisTransportClient, RedisTransportOptions};
42#[cfg(any(
43    feature = "grpc-transport",
44    feature = "kafka-transport",
45    feature = "mqtt-transport",
46    feature = "nats-transport",
47    feature = "rabbitmq-transport",
48    feature = "redis-transport",
49    feature = "tcp-transport"
50))]
51pub(super) fn transport_error_from_status(status: u16, message: String) -> BootError {
52    BootError::from_http_status(status, message)
53}
54
55#[cfg(feature = "tcp-transport")]
56pub use tcp::{TcpTransport, TcpTransportClient, TcpTransportOptions};
57
58/// Adapter-neutral microservice transport message.
59#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
60pub struct TransportMessage {
61    pub pattern: String,
62    #[serde(default)]
63    pub data: Value,
64}
65
66impl TransportMessage {
67    pub fn new(pattern: impl Into<String>, data: impl Into<Value>) -> Self {
68        Self {
69            pattern: pattern.into(),
70            data: data.into(),
71        }
72    }
73
74    pub fn pattern(&self) -> &str {
75        &self.pattern
76    }
77
78    pub fn data(&self) -> &Value {
79        &self.data
80    }
81
82    pub fn text(pattern: impl Into<String>, data: impl Into<String>) -> Self {
83        Self::new(pattern, Value::String(data.into()))
84    }
85
86    pub fn json<T>(pattern: impl Into<String>, data: &T) -> Result<Self>
87    where
88        T: Serialize,
89    {
90        Ok(Self::new(
91            pattern,
92            serde_json::to_value(data).map_err(|err| BootError::Internal(err.to_string()))?,
93        ))
94    }
95
96    pub fn data_as<T>(&self) -> Result<T>
97    where
98        T: DeserializeOwned,
99    {
100        serde_json::from_value(self.data.clone())
101            .map_err(|err| BootError::BadRequest(err.to_string()))
102    }
103
104    pub fn data_field(&self, name: &str) -> Result<Option<Value>> {
105        let Value::Object(fields) = &self.data else {
106            return Err(BootError::BadRequest(
107                "expected JSON object transport data".to_string(),
108            ));
109        };
110
111        Ok(fields.get(name).filter(|value| !value.is_null()).cloned())
112    }
113
114    pub fn data_field_as<T>(&self, name: &str) -> Result<T>
115    where
116        T: DeserializeOwned,
117    {
118        let Some(value) = self.data_field(name)? else {
119            return Err(BootError::BadRequest(format!(
120                "missing transport data field: {name}"
121            )));
122        };
123        deserialize_data_field("transport data field", name, value)
124    }
125
126    pub fn optional_data_field_as<T>(&self, name: &str) -> Result<Option<T>>
127    where
128        T: DeserializeOwned,
129    {
130        self.data_field(name)?
131            .map(|value| deserialize_data_field("transport data field", name, value))
132            .transpose()
133    }
134
135    pub fn data_field_string(&self, name: &str) -> Result<String> {
136        let Some(value) = self.data_field(name)? else {
137            return Err(BootError::BadRequest(format!(
138                "missing transport data field: {name}"
139            )));
140        };
141        data_field_value_to_string(value)
142    }
143
144    pub fn optional_data_field_string(&self, name: &str) -> Result<Option<String>> {
145        self.data_field(name)?
146            .map(data_field_value_to_string)
147            .transpose()
148    }
149
150    pub fn validated_data<T>(&self) -> Result<T>
151    where
152        T: DeserializeOwned + Validate,
153    {
154        validate_value(self.data_as::<T>()?)
155    }
156}
157
158fn deserialize_data_field<T>(label: &str, name: &str, value: Value) -> Result<T>
159where
160    T: DeserializeOwned,
161{
162    serde_json::from_value(value)
163        .map_err(|error| BootError::BadRequest(format!("invalid {label} {name}: {error}")))
164}
165
166fn data_field_value_to_string(value: Value) -> Result<String> {
167    match value {
168        Value::String(value) => Ok(value),
169        Value::Bool(value) => Ok(value.to_string()),
170        Value::Number(value) => Ok(value.to_string()),
171        Value::Array(_) | Value::Object(_) => {
172            serde_json::to_string(&value).map_err(|error| BootError::BadRequest(error.to_string()))
173        }
174        Value::Null => Ok("null".to_string()),
175    }
176}
177
178/// Reply returned by request-response message pattern handlers.
179#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
180pub struct TransportReply {
181    #[serde(default)]
182    pub data: Value,
183}
184
185impl TransportReply {
186    pub fn new(data: impl Into<Value>) -> Self {
187        Self { data: data.into() }
188    }
189
190    pub fn data(&self) -> &Value {
191        &self.data
192    }
193
194    pub fn text(data: impl Into<String>) -> Self {
195        Self::new(Value::String(data.into()))
196    }
197
198    pub fn json<T>(data: &T) -> Result<Self>
199    where
200        T: Serialize,
201    {
202        Ok(Self::new(
203            serde_json::to_value(data).map_err(|err| BootError::Internal(err.to_string()))?,
204        ))
205    }
206
207    pub fn data_as<T>(&self) -> Result<T>
208    where
209        T: DeserializeOwned,
210    {
211        serde_json::from_value(self.data.clone())
212            .map_err(|err| BootError::BadRequest(err.to_string()))
213    }
214}
215
216/// Return value accepted by request-response message pattern handlers.
217pub trait IntoTransportReply {
218    fn into_transport_reply(self) -> Option<TransportReply>;
219}
220
221impl IntoTransportReply for TransportReply {
222    fn into_transport_reply(self) -> Option<TransportReply> {
223        Some(self)
224    }
225}
226
227impl IntoTransportReply for Option<TransportReply> {
228    fn into_transport_reply(self) -> Option<TransportReply> {
229        self
230    }
231}
232
233impl IntoTransportReply for Value {
234    fn into_transport_reply(self) -> Option<TransportReply> {
235        Some(TransportReply::new(self))
236    }
237}
238
239impl IntoTransportReply for Option<Value> {
240    fn into_transport_reply(self) -> Option<TransportReply> {
241        self.map(TransportReply::new)
242    }
243}
244
245impl IntoTransportReply for () {
246    fn into_transport_reply(self) -> Option<TransportReply> {
247        None
248    }
249}
250
251#[derive(Debug, Clone, Copy, PartialEq, Eq)]
252pub enum MessagePatternKind {
253    RequestResponse,
254    Event,
255}
256
257impl MessagePatternKind {
258    pub fn as_str(self) -> &'static str {
259        match self {
260            Self::RequestResponse => "request-response",
261            Self::Event => "event",
262        }
263    }
264}
265
266/// Context available to transport guards and interceptors.
267#[derive(Debug, Clone)]
268pub struct TransportContext {
269    pub pattern: String,
270    pub kind: MessagePatternKind,
271    pub module_name: Option<String>,
272    execution_context: ExecutionContext,
273}
274
275impl TransportContext {
276    fn new(definition: &MessagePatternDefinition, pattern: &str) -> Self {
277        let pattern = pattern.to_string();
278        let kind = definition.kind;
279        let module_name = definition.module_name.clone();
280        let metadata = definition.metadata.clone();
281        let execution_context = ExecutionContext::transport(
282            pattern.clone(),
283            ExecutionTransportKind::from(kind),
284            module_name.clone(),
285            metadata,
286        );
287        Self {
288            pattern,
289            kind,
290            module_name,
291            execution_context,
292        }
293    }
294
295    pub fn execution_context(&self) -> &ExecutionContext {
296        &self.execution_context
297    }
298
299    pub fn into_execution_context(self) -> ExecutionContext {
300        self.execution_context
301    }
302}
303
304impl Deref for TransportContext {
305    type Target = ExecutionContext;
306
307    fn deref(&self) -> &Self::Target {
308        self.execution_context()
309    }
310}
311
312impl From<MessagePatternKind> for ExecutionTransportKind {
313    fn from(kind: MessagePatternKind) -> Self {
314        match kind {
315            MessagePatternKind::RequestResponse => Self::RequestResponse,
316            MessagePatternKind::Event => Self::Event,
317        }
318    }
319}
320
321/// Message transformation hook for transport message patterns.
322pub trait TransportPipe: Send + Sync + 'static {
323    fn transform(&self, message: TransportMessage) -> BoxFuture<'static, Result<TransportMessage>>;
324}
325
326impl<F, Fut> TransportPipe for F
327where
328    F: Fn(TransportMessage) -> Fut + Send + Sync + 'static,
329    Fut: Future<Output = Result<TransportMessage>> + Send + 'static,
330{
331    fn transform(&self, message: TransportMessage) -> BoxFuture<'static, Result<TransportMessage>> {
332        Box::pin(self(message))
333    }
334}
335
336/// Authorization hook for transport message patterns.
337pub trait TransportGuard: Send + Sync + 'static {
338    fn can_activate(&self, context: TransportContext) -> BoxFuture<'static, Result<bool>>;
339}
340
341impl<F, Fut> TransportGuard for F
342where
343    F: Fn(TransportContext) -> Fut + Send + Sync + 'static,
344    Fut: Future<Output = Result<bool>> + Send + 'static,
345{
346    fn can_activate(&self, context: TransportContext) -> BoxFuture<'static, Result<bool>> {
347        Box::pin(self(context))
348    }
349}
350
351struct ExecutionTransportGuard<G> {
352    inner: G,
353}
354
355impl<G> TransportGuard for ExecutionTransportGuard<G>
356where
357    G: Guard,
358{
359    fn can_activate(&self, context: TransportContext) -> BoxFuture<'static, Result<bool>> {
360        self.inner.can_activate(context.into_execution_context())
361    }
362}
363
364/// Around-handler hook for transport message patterns.
365pub trait TransportInterceptor: Send + Sync + 'static {
366    fn before(&self, _context: TransportContext) -> BoxFuture<'static, Result<()>> {
367        Box::pin(async { Ok(()) })
368    }
369
370    fn after(
371        &self,
372        _context: TransportContext,
373        reply: Option<TransportReply>,
374    ) -> BoxFuture<'static, Result<Option<TransportReply>>> {
375        Box::pin(async move { Ok(reply) })
376    }
377}
378
379struct ExecutionTransportInterceptor<I> {
380    inner: I,
381}
382
383impl<I> TransportInterceptor for ExecutionTransportInterceptor<I>
384where
385    I: ExecutionInterceptor,
386{
387    fn before(&self, context: TransportContext) -> BoxFuture<'static, Result<()>> {
388        self.inner.before(context.into_execution_context())
389    }
390
391    fn after(
392        &self,
393        context: TransportContext,
394        reply: Option<TransportReply>,
395    ) -> BoxFuture<'static, Result<Option<TransportReply>>> {
396        let future = self.inner.after(context.into_execution_context());
397        Box::pin(async move {
398            future.await?;
399            Ok(reply)
400        })
401    }
402}
403
404type TransportHandlerFuture = BoxFuture<'static, Result<Option<TransportReply>>>;
405type MessageValidator =
406    Arc<dyn Fn(TransportMessage, ValidationOptions) -> Result<TransportMessage> + Send + Sync>;
407
408trait TransportMessageHandler: Send + Sync + 'static {
409    fn call(&self, message: TransportMessage) -> TransportHandlerFuture;
410}
411
412struct TransportHandlerAdapter<H> {
413    handler: H,
414}
415
416impl<H, Fut, R> TransportMessageHandler for TransportHandlerAdapter<H>
417where
418    H: Fn(TransportMessage) -> Fut + Send + Sync + 'static,
419    Fut: Future<Output = Result<R>> + Send + 'static,
420    R: IntoTransportReply + Send + 'static,
421{
422    fn call(&self, message: TransportMessage) -> TransportHandlerFuture {
423        let future = (self.handler)(message);
424        Box::pin(async move { Ok(future.await?.into_transport_reply()) })
425    }
426}
427
428/// Framework-neutral message pattern handler definition.
429#[derive(Clone)]
430pub struct MessagePatternDefinition {
431    pattern: String,
432    kind: MessagePatternKind,
433    handler: Arc<dyn TransportMessageHandler>,
434    pipes: Vec<PipelineComponent<dyn TransportPipe>>,
435    guards: Vec<PipelineComponent<dyn TransportGuard>>,
436    interceptors: Vec<PipelineComponent<dyn TransportInterceptor>>,
437    filters: Vec<PipelineComponent<dyn TransportExceptionFilter>>,
438    validators: Vec<MessageValidator>,
439    validation_enabled: bool,
440    validation_disabled: bool,
441    validation_options: ValidationOptions,
442    metadata: BTreeMap<String, Value>,
443    module_name: Option<String>,
444}
445
446impl MessagePatternDefinition {
447    pub fn request<H, Fut, R>(pattern: impl Into<String>, handler: H) -> Result<Self>
448    where
449        H: Fn(TransportMessage) -> Fut + Send + Sync + 'static,
450        Fut: Future<Output = Result<R>> + Send + 'static,
451        R: IntoTransportReply + Send + 'static,
452    {
453        Self::new(pattern, MessagePatternKind::RequestResponse, handler)
454    }
455
456    pub fn event<H, Fut>(pattern: impl Into<String>, handler: H) -> Result<Self>
457    where
458        H: Fn(TransportMessage) -> Fut + Send + Sync + 'static,
459        Fut: Future<Output = Result<()>> + Send + 'static,
460    {
461        Self::new(pattern, MessagePatternKind::Event, handler)
462    }
463
464    pub fn request_json<T, H, Fut, R>(pattern: impl Into<String>, handler: H) -> Result<Self>
465    where
466        T: DeserializeOwned + Send + 'static,
467        H: Fn(T) -> Fut + Send + Sync + 'static,
468        Fut: Future<Output = Result<R>> + Send + 'static,
469        R: Serialize + Send + 'static,
470    {
471        Self::request(pattern, move |message: TransportMessage| {
472            let payload = message.data_as::<T>();
473            let future = payload.map(&handler);
474            async move {
475                let response = future?.await?;
476                TransportReply::json(&response)
477            }
478        })
479    }
480
481    pub fn request_validated_json<T, H, Fut, R>(
482        pattern: impl Into<String>,
483        handler: H,
484    ) -> Result<Self>
485    where
486        T: DeserializeOwned + Validate + Send + 'static,
487        H: Fn(T) -> Fut + Send + Sync + 'static,
488        Fut: Future<Output = Result<R>> + Send + 'static,
489        R: Serialize + Send + 'static,
490    {
491        Self::request(pattern, move |message: TransportMessage| {
492            let payload = message.validated_data::<T>();
493            let future = payload.map(&handler);
494            async move {
495                let response = future?.await?;
496                TransportReply::json(&response)
497            }
498        })
499    }
500
501    pub fn event_json<T, H, Fut>(pattern: impl Into<String>, handler: H) -> Result<Self>
502    where
503        T: DeserializeOwned + Send + 'static,
504        H: Fn(T) -> Fut + Send + Sync + 'static,
505        Fut: Future<Output = Result<()>> + Send + 'static,
506    {
507        Self::event(pattern, move |message: TransportMessage| {
508            let payload = message.data_as::<T>();
509            let future = payload.map(&handler);
510            async move { future?.await }
511        })
512    }
513
514    pub fn event_validated_json<T, H, Fut>(pattern: impl Into<String>, handler: H) -> Result<Self>
515    where
516        T: DeserializeOwned + Validate + Send + 'static,
517        H: Fn(T) -> Fut + Send + Sync + 'static,
518        Fut: Future<Output = Result<()>> + Send + 'static,
519    {
520        Self::event(pattern, move |message: TransportMessage| {
521            let payload = message.validated_data::<T>();
522            let future = payload.map(&handler);
523            async move { future?.await }
524        })
525    }
526
527    fn new<H, Fut, R>(
528        pattern: impl Into<String>,
529        kind: MessagePatternKind,
530        handler: H,
531    ) -> Result<Self>
532    where
533        H: Fn(TransportMessage) -> Fut + Send + Sync + 'static,
534        Fut: Future<Output = Result<R>> + Send + 'static,
535        R: IntoTransportReply + Send + 'static,
536    {
537        let pattern = pattern.into();
538        validate_pattern(&pattern)?;
539        Ok(Self {
540            pattern,
541            kind,
542            handler: Arc::new(TransportHandlerAdapter { handler }),
543            pipes: Vec::new(),
544            guards: Vec::new(),
545            interceptors: Vec::new(),
546            filters: Vec::new(),
547            validators: Vec::new(),
548            validation_enabled: false,
549            validation_disabled: false,
550            validation_options: ValidationOptions::default(),
551            metadata: BTreeMap::new(),
552            module_name: None,
553        })
554    }
555
556    pub fn pattern(&self) -> &str {
557        &self.pattern
558    }
559
560    pub fn kind(&self) -> MessagePatternKind {
561        self.kind
562    }
563
564    pub fn module_name(&self) -> Option<&str> {
565        self.module_name.as_deref()
566    }
567
568    pub fn metadata(&self) -> &BTreeMap<String, Value> {
569        &self.metadata
570    }
571
572    pub fn metadata_value(&self, key: &str) -> Option<&Value> {
573        self.metadata.get(key)
574    }
575
576    pub fn with_metadata<V>(self, key: impl Into<String>, value: V) -> Result<Self>
577    where
578        V: Serialize,
579    {
580        let key = key.into();
581        let value = serde_json::to_value(value).map_err(|error| {
582            BootError::Internal(format!(
583                "failed to serialize message pattern metadata `{key}`: {error}"
584            ))
585        })?;
586        Ok(self.with_metadata_value(key, value))
587    }
588
589    pub fn with_metadata_value(mut self, key: impl Into<String>, value: Value) -> Self {
590        self.metadata.insert(key.into(), value);
591        self
592    }
593
594    pub fn with_pipe<P>(mut self, pipe: P) -> Self
595    where
596        P: TransportPipe,
597    {
598        self.pipes
599            .push(PipelineComponent::<dyn TransportPipe>::new(pipe));
600        self
601    }
602
603    pub fn with_guard<G>(mut self, guard: G) -> Self
604    where
605        G: TransportGuard,
606    {
607        self.guards
608            .push(PipelineComponent::<dyn TransportGuard>::new(guard));
609        self
610    }
611
612    pub fn with_execution_guard<G>(mut self, guard: G) -> Self
613    where
614        G: Guard,
615    {
616        self.guards
617            .push(PipelineComponent::<dyn TransportGuard>::new(
618                ExecutionTransportGuard { inner: guard },
619            ));
620        self
621    }
622
623    pub(crate) fn with_execution_pipeline_prefix(
624        mut self,
625        guards: &[Arc<dyn Guard>],
626        interceptors: &[Arc<dyn ExecutionInterceptor>],
627    ) -> Self {
628        self.guards = prepend_execution_guards(guards, self.guards);
629        self.interceptors = prepend_execution_interceptors(interceptors, self.interceptors);
630        self
631    }
632
633    pub(crate) fn with_guard_prefix(mut self, guards: &[Arc<dyn TransportGuard>]) -> Self {
634        let mut merged = guards
635            .iter()
636            .cloned()
637            .map(PipelineComponent::<dyn TransportGuard>::from_arc)
638            .collect::<Vec<_>>();
639        merged.extend(self.guards);
640        self.guards = merged;
641        self
642    }
643
644    pub(crate) fn with_interceptor_prefix(
645        mut self,
646        interceptors: &[Arc<dyn TransportInterceptor>],
647    ) -> Self {
648        let mut merged = interceptors
649            .iter()
650            .cloned()
651            .map(PipelineComponent::<dyn TransportInterceptor>::from_arc)
652            .collect::<Vec<_>>();
653        merged.extend(self.interceptors);
654        self.interceptors = merged;
655        self
656    }
657
658    pub(crate) fn with_pipe_prefix(mut self, pipes: &[Arc<dyn TransportPipe>]) -> Self {
659        let mut merged = pipes
660            .iter()
661            .cloned()
662            .map(PipelineComponent::<dyn TransportPipe>::from_arc)
663            .collect::<Vec<_>>();
664        merged.extend(self.pipes);
665        self.pipes = merged;
666        self
667    }
668
669    pub(crate) fn with_filter_prefix(
670        mut self,
671        filters: &[Arc<dyn TransportExceptionFilter>],
672    ) -> Self {
673        let mut merged = filters
674            .iter()
675            .cloned()
676            .map(PipelineComponent::<dyn TransportExceptionFilter>::from_arc)
677            .collect::<Vec<_>>();
678        merged.extend(self.filters);
679        self.filters = merged;
680        self
681    }
682
683    pub fn with_interceptor<I>(mut self, interceptor: I) -> Self
684    where
685        I: TransportInterceptor,
686    {
687        self.interceptors
688            .push(PipelineComponent::<dyn TransportInterceptor>::new(
689                interceptor,
690            ));
691        self
692    }
693
694    pub fn with_execution_interceptor<I>(mut self, interceptor: I) -> Self
695    where
696        I: ExecutionInterceptor,
697    {
698        self.interceptors
699            .push(PipelineComponent::<dyn TransportInterceptor>::new(
700                ExecutionTransportInterceptor { inner: interceptor },
701            ));
702        self
703    }
704
705    pub fn with_filter<F>(mut self, filter: F) -> Self
706    where
707        F: TransportExceptionFilter,
708    {
709        self.filters
710            .push(PipelineComponent::<dyn TransportExceptionFilter>::new(
711                filter,
712            ));
713        self
714    }
715
716    pub fn with_catch_filter<I, F>(self, kinds: I, filter: F) -> Self
717    where
718        I: IntoIterator<Item = BootErrorKind>,
719        F: TransportExceptionFilter,
720    {
721        self.with_filter(catch_errors(kinds, filter))
722    }
723
724    pub(crate) fn with_pipeline_overrides(mut self, overrides: &PipelineOverrides) -> Self {
725        overrides.apply_to_transport_pipes(&mut self.pipes);
726        overrides.apply_to_transport_guards(&mut self.guards);
727        overrides.apply_to_transport_interceptors(&mut self.interceptors);
728        overrides.apply_to_transport_filters(&mut self.filters);
729        self
730    }
731
732    pub fn with_validation(mut self) -> Self {
733        self.validation_enabled = true;
734        self.validation_disabled = false;
735        self
736    }
737
738    pub fn with_validation_options(mut self, options: ValidationOptions) -> Self {
739        self.validation_enabled = true;
740        self.validation_disabled = false;
741        self.validation_options = self.validation_options.merge(options);
742        self
743    }
744
745    pub fn without_validation(mut self) -> Self {
746        self.validation_enabled = false;
747        self.validation_disabled = true;
748        self
749    }
750
751    pub(crate) fn with_validation_prefix(
752        mut self,
753        validation_enabled: bool,
754        validation_options: ValidationOptions,
755    ) -> Self {
756        if !self.validation_disabled {
757            self.validation_enabled = validation_enabled || self.validation_enabled;
758            self.validation_options = validation_options.merge(self.validation_options);
759        }
760        self
761    }
762
763    pub fn with_payload_validation<T>(mut self) -> Self
764    where
765        T: DeserializeOwned + Validate + 'static,
766    {
767        self.validators.push(Arc::new(|message, _| {
768            message.validated_data::<T>().map(|_| message)
769        }));
770        self.with_validation()
771    }
772
773    pub fn with_payload_validation_options<T>(mut self, options: ValidationOptions) -> Self
774    where
775        T: DeserializeOwned + Serialize + Validate + ValidationSchema + 'static,
776    {
777        self.validators
778            .push(Arc::new(move |mut message, inherited_options| {
779                let options = inherited_options.merge(options);
780                let data = validate_json_value_with_options::<T>(
781                    message.data.clone(),
782                    options,
783                    "message property",
784                )?;
785                if options.transform || options.whitelist {
786                    message.data = data;
787                }
788                Ok(message)
789            }));
790        self.with_validation()
791    }
792
793    pub async fn dispatch(&self, message: TransportMessage) -> Result<Option<TransportReply>> {
794        let context = TransportContext::new(self, &message.pattern);
795        match self.dispatch_pipeline(message, context.clone()).await {
796            Ok(reply) => Ok(reply),
797            Err(error) => self.handle_error(context, error).await,
798        }
799    }
800
801    async fn dispatch_pipeline(
802        &self,
803        mut message: TransportMessage,
804        context: TransportContext,
805    ) -> Result<Option<TransportReply>> {
806        if message.pattern != self.pattern {
807            return Err(BootError::NotFound(format!(
808                "message pattern {}",
809                message.pattern
810            )));
811        }
812
813        for guard in &self.guards {
814            let can_activate = guard.inner().can_activate(context.clone()).await?;
815            if !can_activate {
816                return Err(BootError::Forbidden(format!(
817                    "message pattern {}",
818                    message.pattern
819                )));
820            }
821        }
822
823        for interceptor in &self.interceptors {
824            interceptor.inner().before(context.clone()).await?;
825        }
826
827        for pipe in &self.pipes {
828            message = pipe.inner().transform(message).await?;
829        }
830
831        if self.validation_enabled {
832            for validator in &self.validators {
833                message = validator(message, self.validation_options)?;
834            }
835        }
836
837        let mut reply = self.handler.call(message).await?;
838        if self.kind == MessagePatternKind::Event {
839            reply = None;
840        }
841
842        for interceptor in self.interceptors.iter().rev() {
843            reply = interceptor.inner().after(context.clone(), reply).await?;
844        }
845        Ok(reply)
846    }
847
848    async fn handle_error(
849        &self,
850        context: TransportContext,
851        error: BootError,
852    ) -> Result<Option<TransportReply>> {
853        for filter in self.filters.iter().rev() {
854            if let Some(response) = filter
855                .inner()
856                .catch(context.clone(), error.clone_for_filter())
857                .await?
858            {
859                return Ok(if self.kind == MessagePatternKind::Event {
860                    None
861                } else {
862                    response.into_reply()
863                });
864            }
865        }
866        Err(error)
867    }
868
869    pub(crate) fn with_module_name(mut self, module_name: &str) -> Self {
870        self.module_name = Some(module_name.to_string());
871        self
872    }
873}
874
875fn prepend_execution_guards(
876    prefix: &[Arc<dyn Guard>],
877    values: Vec<PipelineComponent<dyn TransportGuard>>,
878) -> Vec<PipelineComponent<dyn TransportGuard>> {
879    let mut merged = prefix
880        .iter()
881        .cloned()
882        .map(|guard| {
883            PipelineComponent::<dyn TransportGuard>::new(ExecutionTransportGuard { inner: guard })
884        })
885        .collect::<Vec<_>>();
886    merged.extend(values);
887    merged
888}
889
890fn prepend_execution_interceptors(
891    prefix: &[Arc<dyn ExecutionInterceptor>],
892    values: Vec<PipelineComponent<dyn TransportInterceptor>>,
893) -> Vec<PipelineComponent<dyn TransportInterceptor>> {
894    let mut merged = prefix
895        .iter()
896        .cloned()
897        .map(|interceptor| {
898            PipelineComponent::<dyn TransportInterceptor>::new(ExecutionTransportInterceptor {
899                inner: interceptor,
900            })
901        })
902        .collect::<Vec<_>>();
903    merged.extend(values);
904    merged
905}
906
907/// Adapter trait for message transports such as in-process, Redis, NATS, or Kafka.
908pub trait MessageTransport {
909    type Output;
910
911    fn build(&self, app: crate::BootApplication) -> Result<Self::Output>;
912
913    fn serve(&self, app: crate::BootApplication) -> BoxFuture<'static, Result<()>>;
914}
915
916/// In-process transport useful for tests and single-process message dispatch.
917#[derive(Debug, Clone, Copy, Default)]
918pub struct InProcessTransport;
919
920impl InProcessTransport {
921    pub fn new() -> Self {
922        Self
923    }
924}
925
926impl MessageTransport for InProcessTransport {
927    type Output = InProcessTransportClient;
928
929    fn build(&self, app: crate::BootApplication) -> Result<Self::Output> {
930        Ok(InProcessTransportClient { app })
931    }
932
933    fn serve(&self, _app: crate::BootApplication) -> BoxFuture<'static, Result<()>> {
934        Box::pin(async { Ok(()) })
935    }
936}
937
938/// In-process message client backed by a resolved Boot application.
939#[derive(Clone)]
940pub struct InProcessTransportClient {
941    app: crate::BootApplication,
942}
943
944impl InProcessTransportClient {
945    pub async fn send(&self, message: TransportMessage) -> Result<Option<TransportReply>> {
946        self.app.dispatch_message(message).await
947    }
948
949    pub async fn emit(&self, message: TransportMessage) -> Result<()> {
950        self.app.emit_message(message).await
951    }
952}
953
954fn validate_pattern(pattern: &str) -> Result<()> {
955    if pattern.trim().is_empty() {
956        return Err(BootError::BadRequest(
957            "message pattern cannot be empty".to_string(),
958        ));
959    }
960    Ok(())
961}