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;