Skip to main content

agent_client_protocol/
util.rs

1// Types re-exported from crate root
2
3use futures::{
4    future::BoxFuture,
5    stream::{Stream, StreamExt},
6};
7
8mod typed;
9pub use typed::{MatchDispatch, MatchDispatchFrom, TypeNotification};
10
11/// Cast from `N` to `M` by serializing/deserialization to/from JSON.
12pub fn json_cast<N, M>(params: N) -> Result<M, crate::Error>
13where
14    N: serde::Serialize,
15    M: serde::de::DeserializeOwned,
16{
17    let json = serde_json::to_value(params).map_err(|e| {
18        crate::Error::parse_error().data(serde_json::json!({
19            "error": e.to_string(),
20            "phase": "serialization"
21        }))
22    })?;
23    let m = serde_json::from_value(json.clone()).map_err(|e| {
24        crate::Error::parse_error().data(serde_json::json!({
25            "error": e.to_string(),
26            "json": json,
27            "phase": "deserialization"
28        }))
29    })?;
30    Ok(m)
31}
32
33/// Cast incoming request/notification params into a typed payload.
34///
35/// Like [`json_cast`], but deserialization failures become
36/// [`Error::invalid_params`](`crate::Error::invalid_params`) (`-32602`)
37/// instead of a parse error, which is the correct JSON-RPC error code for
38/// malformed method parameters.
39pub fn json_cast_params<N, M>(params: N) -> Result<M, crate::Error>
40where
41    N: serde::Serialize,
42    M: serde::de::DeserializeOwned,
43{
44    let json = serde_json::to_value(params).map_err(|e| {
45        crate::Error::internal_error().data(serde_json::json!({
46            "error": e.to_string(),
47            "phase": "serialization"
48        }))
49    })?;
50    let m = serde_json::from_value(json.clone()).map_err(|e| {
51        crate::Error::invalid_params().data(serde_json::json!({
52            "error": e.to_string(),
53            "json": json,
54            "phase": "deserialization"
55        }))
56    })?;
57    Ok(m)
58}
59
60/// Creates an internal error with the given message
61pub fn internal_error(message: impl ToString) -> crate::Error {
62    crate::Error::internal_error().data(message.to_string())
63}
64
65/// Creates a parse error with the given message
66pub fn parse_error(message: impl ToString) -> crate::Error {
67    crate::Error::parse_error().data(message.to_string())
68}
69
70pub(crate) fn instrumented_with_connection_name<F>(
71    name: String,
72    task: F,
73) -> tracing::instrument::Instrumented<F> {
74    use tracing::Instrument;
75
76    task.instrument(tracing::info_span!("connection", name = name))
77}
78
79pub(crate) async fn instrument_with_connection_name<R>(
80    name: Option<String>,
81    task: impl Future<Output = R>,
82) -> R {
83    if let Some(name) = name {
84        instrumented_with_connection_name(name.clone(), task).await
85    } else {
86        task.await
87    }
88}
89
90/// Run `background` until `foreground` completes.
91///
92/// Returns the result of `foreground`. If `background` errors before
93/// `foreground` completes, the error is propagated. If `background`
94/// completes with `Ok(())`, we continue waiting for `foreground`.
95pub fn run_until<T, E>(
96    background: impl Future<Output = Result<(), E>>,
97    foreground: impl Future<Output = Result<T, E>>,
98) -> impl Future<Output = Result<T, E>> {
99    use futures::future::{Either, select};
100    use std::pin::pin;
101
102    Box::pin(async move {
103        match select(pin!(background), pin!(foreground)).await {
104            Either::Left((bg_result, fg_future)) => {
105                // Background finished first
106                bg_result?; // propagate error, or if Ok(()), keep waiting
107                fg_future.await
108            }
109            Either::Right((fg_result, _bg_future)) => {
110                // Foreground finished first, drop background
111                fg_result
112            }
113        }
114    })
115}
116
117/// Process items from a stream concurrently.
118///
119/// For each item received from `stream`, calls `process_fn` to create a future,
120/// then runs all futures concurrently. If any future returns an error,
121/// stops processing and returns that error.
122///
123/// This is useful for patterns where you receive work items from a channel
124/// and want to process them concurrently while respecting backpressure.
125pub(crate) async fn process_stream_concurrently<T, F>(
126    stream: impl Stream<Item = T>,
127    process_fn: F,
128    process_fn_hack: impl for<'a> Fn(&'a F, T) -> BoxFuture<'a, Result<(), crate::Error>>,
129) -> Result<(), crate::Error>
130where
131    F: AsyncFn(T) -> Result<(), crate::Error>,
132{
133    use std::pin::pin;
134
135    use futures::stream::{FusedStream, FuturesUnordered};
136    use futures_concurrency::future::Race;
137
138    enum Event<T> {
139        NewItem(Option<T>),
140        FutureCompleted(Option<Result<(), crate::Error>>),
141    }
142
143    let mut stream = pin!(stream.fuse());
144    let mut futures: FuturesUnordered<_> = FuturesUnordered::new();
145
146    loop {
147        // If we have no futures to run, wait until we do.
148        if futures.is_empty() {
149            match stream.next().await {
150                Some(item) => futures.push(process_fn_hack(&process_fn, item)),
151                None => return Ok(()),
152            }
153            continue;
154        }
155
156        // If there are no more items coming in, just drain our queue and return.
157        if stream.is_terminated() {
158            while let Some(result) = futures.next().await {
159                result?;
160            }
161            return Ok(());
162        }
163
164        // Otherwise, race between getting a new item and completing a future.
165        let event = (async { Event::NewItem(stream.next().await) }, async {
166            Event::FutureCompleted(futures.next().await)
167        })
168            .race()
169            .await;
170
171        match event {
172            Event::NewItem(Some(item)) => {
173                futures.push(process_fn_hack(&process_fn, item));
174            }
175            Event::FutureCompleted(Some(result)) => {
176                result?;
177            }
178            Event::NewItem(None) | Event::FutureCompleted(None) => {
179                // Stream closed, loop will catch is_terminated
180                // No futures were pending, shouldn't happen since we checked is_empty
181            }
182        }
183    }
184}