Skip to main content

a2a_protocol_server/
interceptor.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Tom F.
3
4//! Server-side interceptor chain.
5//!
6//! [`ServerInterceptor`] allows middleware-style hooks before and after each
7//! JSON-RPC or REST method invocation. [`ServerInterceptorChain`] manages an
8//! ordered list of interceptors and runs them sequentially.
9
10use std::future::Future;
11use std::pin::Pin;
12use std::sync::Arc;
13
14use a2a_protocol_types::error::A2aResult;
15
16use crate::call_context::CallContext;
17
18/// A server-side interceptor for request processing.
19///
20/// Interceptors run before and after the core handler logic. They can be used
21/// for logging, authentication, rate-limiting, or other cross-cutting concerns.
22///
23/// # Object safety
24///
25/// This trait is designed to be used behind `Arc<dyn ServerInterceptor>`.
26pub trait ServerInterceptor: Send + Sync + 'static {
27    /// Called before the request handler processes the method call.
28    ///
29    /// Return `Err(...)` to abort the request with an error response.
30    ///
31    /// # Errors
32    ///
33    /// Returns an [`A2aError`](a2a_protocol_types::error::A2aError) to reject the request.
34    fn before<'a>(
35        &'a self,
36        ctx: &'a CallContext,
37    ) -> Pin<Box<dyn Future<Output = A2aResult<()>> + Send + 'a>>;
38
39    /// Called after the request handler has finished processing.
40    ///
41    /// This is called even if the handler returned an error. It should not
42    /// alter the response — use it for logging, metrics, or cleanup.
43    ///
44    /// # Errors
45    ///
46    /// Returns an [`A2aError`](a2a_protocol_types::error::A2aError) if post-processing fails.
47    fn after<'a>(
48        &'a self,
49        ctx: &'a CallContext,
50    ) -> Pin<Box<dyn Future<Output = A2aResult<()>> + Send + 'a>>;
51}
52
53/// An ordered chain of [`ServerInterceptor`] instances.
54///
55/// Interceptors are executed in insertion order for `before` and reverse order
56/// for `after`.
57#[derive(Default)]
58pub struct ServerInterceptorChain {
59    interceptors: Vec<Arc<dyn ServerInterceptor>>,
60}
61
62impl ServerInterceptorChain {
63    /// Creates an empty interceptor chain.
64    #[must_use]
65    pub fn new() -> Self {
66        Self::default()
67    }
68
69    /// Appends an interceptor to the chain.
70    pub fn push(&mut self, interceptor: Arc<dyn ServerInterceptor>) {
71        self.interceptors.push(interceptor);
72    }
73
74    /// Runs all `before` hooks in insertion order.
75    ///
76    /// Stops at the first error and returns it.
77    ///
78    /// # Errors
79    ///
80    /// Returns the first [`A2aError`](a2a_protocol_types::error::A2aError) from any interceptor.
81    pub async fn run_before(&self, ctx: &CallContext) -> A2aResult<()> {
82        for interceptor in &self.interceptors {
83            interceptor.before(ctx).await?;
84        }
85        Ok(())
86    }
87
88    /// Runs all `after` hooks in reverse insertion order.
89    ///
90    /// Stops at the first error and returns it.
91    ///
92    /// # Errors
93    ///
94    /// Returns the first [`A2aError`](a2a_protocol_types::error::A2aError) from any interceptor.
95    pub async fn run_after(&self, ctx: &CallContext) -> A2aResult<()> {
96        for interceptor in self.interceptors.iter().rev() {
97            interceptor.after(ctx).await?;
98        }
99        Ok(())
100    }
101}
102
103impl fmt::Debug for ServerInterceptorChain {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        f.debug_struct("ServerInterceptorChain")
106            .field("count", &self.interceptors.len())
107            .finish()
108    }
109}
110
111use std::fmt;