Skip to main content

agent_client_protocol/jsonrpc/
handlers.rs

1use crate::jsonrpc::{
2    HandleDispatchFrom, Handled, IntoHandled, JsonRpcResponse, Protocol, protocol,
3};
4
5use crate::role::{HasPeer, Role, handle_incoming_dispatch};
6use crate::{ConnectionTo, Dispatch, JsonRpcNotification, JsonRpcRequest, UntypedMessage};
7// Types re-exported from crate root
8use super::Responder;
9use std::future::Future;
10use std::marker::PhantomData;
11use std::ops::AsyncFnMut;
12
13/// Null handler that accepts no messages.
14#[derive(Debug)]
15pub struct NullHandler;
16
17impl Default for NullHandler {
18    fn default() -> Self {
19        Self
20    }
21}
22
23impl<Counterpart: Role> HandleDispatchFrom<Counterpart> for NullHandler {
24    fn describe_chain(&self) -> impl std::fmt::Debug {
25        "(null)"
26    }
27
28    fn handle_dispatch_from(
29        &mut self,
30        message: Dispatch,
31        _cx: ConnectionTo<Counterpart>,
32    ) -> impl Future<Output = Result<Handled<Dispatch>, crate::Error>> + Send {
33        std::future::ready(Ok(Handled::No {
34            message,
35            retry: false,
36        }))
37    }
38}
39
40/// Handler for typed request messages
41pub struct RequestHandler<
42    Counterpart: Role,
43    Peer: Role,
44    P,
45    Req: JsonRpcRequest = UntypedMessage,
46    F = (),
47    ToFut = (),
48> {
49    counterpart: Counterpart,
50    peer: Peer,
51    handler: F,
52    to_future_hack: ToFut,
53    phantom: PhantomData<fn(Req, P)>,
54}
55
56impl<Counterpart: Role, Peer: Role, Req: JsonRpcRequest, F, ToFut, P>
57    RequestHandler<Counterpart, Peer, P, Req, F, ToFut>
58{
59    /// Creates a new request handler
60    pub fn new(counterpart: Counterpart, peer: Peer, handler: F, to_future_hack: ToFut) -> Self {
61        Self {
62            counterpart,
63            peer,
64            handler,
65            to_future_hack,
66            phantom: PhantomData,
67        }
68    }
69}
70
71impl<Counterpart: Role, Peer: Role, Req, F, T, ToFut, P> HandleDispatchFrom<Counterpart>
72    for RequestHandler<Counterpart, Peer, P, Req, F, ToFut>
73where
74    Counterpart: HasPeer<Peer>,
75    Req: JsonRpcRequest,
76    P: Protocol,
77    F: AsyncFnMut(
78            Req,
79            Responder<Req::Response, P>,
80            P::Connection<Counterpart>,
81        ) -> Result<T, crate::Error>
82        + Send,
83    T: crate::IntoHandled<(Req, Responder<Req::Response, P>)>,
84    ToFut: Fn(
85            &mut F,
86            Req,
87            Responder<Req::Response, P>,
88            P::Connection<Counterpart>,
89        ) -> crate::BoxFuture<'_, Result<T, crate::Error>>
90        + Send
91        + Sync,
92{
93    fn describe_chain(&self) -> impl std::fmt::Debug {
94        std::any::type_name::<Req>()
95    }
96
97    async fn handle_dispatch_from(
98        &mut self,
99        dispatch: Dispatch,
100        connection: ConnectionTo<Counterpart>,
101    ) -> Result<Handled<Dispatch>, crate::Error> {
102        handle_incoming_dispatch(
103            self.counterpart.clone(),
104            self.peer.clone(),
105            dispatch,
106            connection,
107            async |dispatch, connection| {
108                match dispatch {
109                    Dispatch::Request(message, responder) => {
110                        tracing::debug!(
111                            request_type = std::any::type_name::<Req>(),
112                            method = %message.method,
113                            "RequestHandler::handle_request"
114                        );
115                        if Req::matches_method(&message.method) {
116                            match Req::parse_message(&message.method, &message.params) {
117                                Ok(req) => {
118                                    tracing::trace!(
119                                        "RequestHandler::handle_request: parse completed"
120                                    );
121                                    let typed_responder = responder.cast().retag::<P>()?;
122                                    let result = (self.to_future_hack)(
123                                        &mut self.handler,
124                                        req,
125                                        typed_responder,
126                                        protocol::connection_from_raw::<P, _>(connection),
127                                    )
128                                    .await?;
129                                    match result.into_handled() {
130                                        Handled::Yes => Ok(Handled::Yes),
131                                        Handled::No {
132                                            message: (request, responder),
133                                            retry,
134                                        } => {
135                                            // Handler returned the request back, convert to untyped
136                                            let untyped = request.to_untyped_message()?;
137                                            Ok(Handled::No {
138                                                message: Dispatch::Request(
139                                                    untyped,
140                                                    responder.erase_to_json().into_raw(),
141                                                ),
142                                                retry,
143                                            })
144                                        }
145                                    }
146                                }
147                                Err(err) => {
148                                    tracing::trace!(
149                                        err = ?crate::util::redacted_error(err.code),
150                                        "RequestHandler::handle_request: parse errored"
151                                    );
152                                    Err(err)
153                                }
154                            }
155                        } else {
156                            tracing::trace!("RequestHandler::handle_request: method doesn't match");
157                            Ok(Handled::No {
158                                message: Dispatch::Request(message, responder),
159                                retry: false,
160                            })
161                        }
162                    }
163
164                    Dispatch::Notification(..) | Dispatch::Response(..) => Ok(Handled::No {
165                        message: dispatch,
166                        retry: false,
167                    }),
168                }
169            },
170        )
171        .await
172    }
173}
174
175/// Handler for typed notification messages
176pub struct NotificationHandler<
177    Counterpart: Role,
178    Peer: Role,
179    P,
180    Notif: JsonRpcNotification = UntypedMessage,
181    F = (),
182    ToFut = (),
183> {
184    counterpart: Counterpart,
185    peer: Peer,
186    handler: F,
187    to_future_hack: ToFut,
188    phantom: PhantomData<fn(Notif, P)>,
189}
190
191impl<Counterpart: Role, Peer: Role, Notif: JsonRpcNotification, F, ToFut, P>
192    NotificationHandler<Counterpart, Peer, P, Notif, F, ToFut>
193{
194    /// Creates a new notification handler
195    pub fn new(counterpart: Counterpart, peer: Peer, handler: F, to_future_hack: ToFut) -> Self {
196        Self {
197            counterpart,
198            peer,
199            handler,
200            to_future_hack,
201            phantom: PhantomData,
202        }
203    }
204}
205
206impl<Counterpart: Role, Peer: Role, Notif, F, T, ToFut, P> HandleDispatchFrom<Counterpart>
207    for NotificationHandler<Counterpart, Peer, P, Notif, F, ToFut>
208where
209    Counterpart: HasPeer<Peer>,
210    Notif: JsonRpcNotification,
211    P: Protocol,
212    F: AsyncFnMut(Notif, P::Connection<Counterpart>) -> Result<T, crate::Error> + Send,
213    T: crate::IntoHandled<(Notif, P::Connection<Counterpart>)>,
214    ToFut: Fn(
215            &mut F,
216            Notif,
217            P::Connection<Counterpart>,
218        ) -> crate::BoxFuture<'_, Result<T, crate::Error>>
219        + Send
220        + Sync,
221{
222    fn describe_chain(&self) -> impl std::fmt::Debug {
223        std::any::type_name::<Notif>()
224    }
225
226    async fn handle_dispatch_from(
227        &mut self,
228        dispatch: Dispatch,
229        connection: ConnectionTo<Counterpart>,
230    ) -> Result<Handled<Dispatch>, crate::Error> {
231        handle_incoming_dispatch(
232            self.counterpart.clone(),
233            self.peer.clone(),
234            dispatch,
235            connection,
236            async |dispatch, connection| {
237                match dispatch {
238                    Dispatch::Notification(message) => {
239                        tracing::debug!(
240                            request_type = std::any::type_name::<Notif>(),
241                            method = %message.method,
242                            "NotificationHandler::handle_dispatch"
243                        );
244                        if Notif::matches_method(&message.method) {
245                            match Notif::parse_message(&message.method, &message.params) {
246                                Ok(notif) => {
247                                    tracing::trace!(
248                                        "NotificationHandler::handle_notification: parse completed"
249                                    );
250                                    let result = (self.to_future_hack)(
251                                        &mut self.handler,
252                                        notif,
253                                        protocol::connection_from_raw::<P, _>(connection),
254                                    )
255                                    .await?;
256                                    match result.into_handled() {
257                                        Handled::Yes => Ok(Handled::Yes),
258                                        Handled::No {
259                                            message: (notification, _cx),
260                                            retry,
261                                        } => {
262                                            // Handler returned the notification back, convert to untyped
263                                            let untyped = notification.to_untyped_message()?;
264                                            Ok(Handled::No {
265                                                message: Dispatch::Notification(untyped),
266                                                retry,
267                                            })
268                                        }
269                                    }
270                                }
271                                Err(err) => {
272                                    tracing::trace!(
273                                        err = ?crate::util::redacted_error(err.code),
274                                        "NotificationHandler::handle_notification: parse errored"
275                                    );
276                                    Err(err)
277                                }
278                            }
279                        } else {
280                            tracing::trace!(
281                                "NotificationHandler::handle_notification: method doesn't match"
282                            );
283                            Ok(Handled::No {
284                                message: Dispatch::Notification(message),
285                                retry: false,
286                            })
287                        }
288                    }
289
290                    Dispatch::Request(..) | Dispatch::Response(..) => Ok(Handled::No {
291                        message: dispatch,
292                        retry: false,
293                    }),
294                }
295            },
296        )
297        .await
298    }
299}
300
301/// Handler for typed requests, notifications, and matching responses.
302pub struct MessageHandler<
303    Counterpart: Role,
304    Peer: Role,
305    P,
306    Req: JsonRpcRequest = UntypedMessage,
307    Notif: JsonRpcNotification = UntypedMessage,
308    F = (),
309    ToFut = (),
310> {
311    counterpart: Counterpart,
312    peer: Peer,
313    handler: F,
314    to_future_hack: ToFut,
315    phantom: PhantomData<fn(Dispatch<Req, Notif>, P)>,
316}
317
318impl<Counterpart: Role, Peer: Role, Req: JsonRpcRequest, Notif: JsonRpcNotification, F, ToFut, P>
319    MessageHandler<Counterpart, Peer, P, Req, Notif, F, ToFut>
320{
321    /// Creates a new message handler
322    pub fn new(counterpart: Counterpart, peer: Peer, handler: F, to_future_hack: ToFut) -> Self {
323        Self {
324            counterpart,
325            peer,
326            handler,
327            to_future_hack,
328            phantom: PhantomData,
329        }
330    }
331}
332
333impl<Counterpart: Role, Peer: Role, Req: JsonRpcRequest, Notif: JsonRpcNotification, F, T, ToFut, P>
334    HandleDispatchFrom<Counterpart> for MessageHandler<Counterpart, Peer, P, Req, Notif, F, ToFut>
335where
336    Counterpart: HasPeer<Peer>,
337    P: Protocol,
338    F: AsyncFnMut(Dispatch<Req, Notif, P>, P::Connection<Counterpart>) -> Result<T, crate::Error>
339        + Send,
340    T: IntoHandled<Dispatch<Req, Notif, P>>,
341    ToFut: Fn(
342            &mut F,
343            Dispatch<Req, Notif, P>,
344            P::Connection<Counterpart>,
345        ) -> crate::BoxFuture<'_, Result<T, crate::Error>>
346        + Send
347        + Sync,
348{
349    fn describe_chain(&self) -> impl std::fmt::Debug {
350        format!(
351            "({}, {})",
352            std::any::type_name::<Req>(),
353            std::any::type_name::<Notif>()
354        )
355    }
356
357    async fn handle_dispatch_from(
358        &mut self,
359        dispatch: Dispatch,
360        connection: ConnectionTo<Counterpart>,
361    ) -> Result<Handled<Dispatch>, crate::Error> {
362        handle_incoming_dispatch(
363            self.counterpart.clone(),
364            self.peer.clone(),
365            dispatch,
366            connection,
367            async |dispatch, connection| match dispatch.into_typed_dispatch::<Req, Notif>()? {
368                Ok(typed_dispatch) => {
369                    let result = (self.to_future_hack)(
370                        &mut self.handler,
371                        typed_dispatch.retag::<P>()?,
372                        protocol::connection_from_raw::<P, _>(connection),
373                    )
374                    .await?;
375                    match result.into_handled() {
376                        Handled::Yes => Ok(Handled::Yes),
377                        Handled::No {
378                            message: Dispatch::Request(request, responder),
379                            retry,
380                        } => {
381                            let untyped = request.to_untyped_message()?;
382                            Ok(Handled::No {
383                                message: Dispatch::Request(
384                                    untyped,
385                                    responder.erase_to_json().into_raw(),
386                                ),
387                                retry,
388                            })
389                        }
390                        Handled::No {
391                            message: Dispatch::Notification(notification),
392                            retry,
393                        } => {
394                            let untyped = notification.to_untyped_message()?;
395                            Ok(Handled::No {
396                                message: Dispatch::Notification(untyped),
397                                retry,
398                            })
399                        }
400                        Handled::No {
401                            message: Dispatch::Response(result, responder),
402                            retry,
403                        } => {
404                            let method = responder.method();
405                            let untyped_result = match result {
406                                Ok(response) => response.into_json(method).map(Ok),
407                                Err(err) => Ok(Err(err)),
408                            }?;
409                            Ok(Handled::No {
410                                message: Dispatch::Response(
411                                    untyped_result,
412                                    responder.erase_to_json().into_raw(),
413                                ),
414                                retry,
415                            })
416                        }
417                    }
418                }
419
420                Err(dispatch) => Ok(Handled::No {
421                    message: dispatch,
422                    retry: false,
423                }),
424            },
425        )
426        .await
427    }
428}
429
430/// Wraps a handler with an optional name for tracing/debugging.
431pub struct NamedHandler<H> {
432    name: Option<String>,
433    handler: H,
434}
435
436impl<H> NamedHandler<H> {
437    /// Creates a new named handler
438    pub fn new(name: Option<String>, handler: H) -> Self {
439        Self { name, handler }
440    }
441}
442
443impl<Counterpart: Role, H: HandleDispatchFrom<Counterpart>> HandleDispatchFrom<Counterpart>
444    for NamedHandler<H>
445{
446    fn describe_chain(&self) -> impl std::fmt::Debug {
447        format!(
448            "NamedHandler({:?}, {:?})",
449            self.name,
450            self.handler.describe_chain()
451        )
452    }
453
454    async fn handle_dispatch_from(
455        &mut self,
456        message: Dispatch,
457        connection: ConnectionTo<Counterpart>,
458    ) -> Result<Handled<Dispatch>, crate::Error> {
459        if let Some(name) = &self.name {
460            crate::util::instrumented_with_connection_name(
461                name.clone(),
462                self.handler.handle_dispatch_from(message, connection),
463            )
464            .await
465        } else {
466            self.handler.handle_dispatch_from(message, connection).await
467        }
468    }
469}
470
471/// Chains two handlers together, trying the first handler and falling back to the second
472pub struct ChainedHandler<H1, H2> {
473    handler1: H1,
474    handler2: H2,
475}
476
477impl<H1, H2> ChainedHandler<H1, H2> {
478    /// Creates a new chain handler
479    pub fn new(handler1: H1, handler2: H2) -> Self {
480        Self { handler1, handler2 }
481    }
482}
483
484/// Heap-allocates a handler's dispatch future; `inline(never)` keeps its
485/// construction out of the caller's poll frame.
486#[inline(never)]
487fn boxed_dispatch<Counterpart, H>(
488    handler: &mut H,
489    message: Dispatch,
490    connection: ConnectionTo<Counterpart>,
491) -> crate::BoxFuture<'_, Result<Handled<Dispatch>, crate::Error>>
492where
493    Counterpart: Role,
494    H: HandleDispatchFrom<Counterpart>,
495{
496    Box::pin(handler.handle_dispatch_from(message, connection))
497}
498
499impl<Counterpart: Role, H1, H2> HandleDispatchFrom<Counterpart> for ChainedHandler<H1, H2>
500where
501    H1: HandleDispatchFrom<Counterpart>,
502    H2: HandleDispatchFrom<Counterpart>,
503{
504    fn describe_chain(&self) -> impl std::fmt::Debug {
505        format!(
506            "{:?}, {:?}",
507            self.handler1.describe_chain(),
508            self.handler2.describe_chain()
509        )
510    }
511
512    async fn handle_dispatch_from(
513        &mut self,
514        message: Dispatch,
515        connection: ConnectionTo<Counterpart>,
516    ) -> Result<Handled<Dispatch>, crate::Error> {
517        // Box each link so deep chains don't build one giant nested poll
518        // frame per handler and overflow small (e.g. GCD 512 KiB) stacks.
519        match boxed_dispatch(&mut self.handler1, message, connection.clone()).await? {
520            Handled::Yes => Ok(Handled::Yes),
521            Handled::No {
522                message,
523                retry: retry1,
524            } => match boxed_dispatch(&mut self.handler2, message, connection).await? {
525                Handled::Yes => Ok(Handled::Yes),
526                Handled::No {
527                    message,
528                    retry: retry2,
529                } => Ok(Handled::No {
530                    message,
531                    retry: retry1 | retry2,
532                }),
533            },
534        }
535    }
536}