Skip to main content

lspf/
service.rs

1//! Normalized user dispatch and the fixed Service stack (ADR 0019).
2
3use std::panic::AssertUnwindSafe;
4use std::pin::Pin;
5use std::sync::Arc;
6
7use futures_util::FutureExt;
8use serde_json::Value;
9use tracing::{Instrument, error, info_span};
10
11use crate::builder::Router;
12use crate::sync::Semaphore;
13use crate::{Context, LspError, RequestId, TaskFuture, TaskSend};
14
15/// Whether a normalized user call came from a request or a notification.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum CallKind {
18    /// A call that must resolve to a response or error.
19    Request,
20    /// A fire-and-forget call.
21    Notification,
22}
23
24/// A validated, decoded call at the user Layer boundary.
25///
26/// Protocol framing and lifecycle state are deliberately absent. Layers can
27/// inspect the stable metadata, application state, and decoded JSON value; a
28/// Layer that intentionally shapes parameters can replace [`params_mut`]
29/// without serializing them.
30///
31/// [`params_mut`]: IncomingCall::params_mut
32pub struct IncomingCall<S> {
33    kind: CallKind,
34    method: String,
35    request_id: Option<RequestId>,
36    params: Value,
37    context: Context,
38    state: Arc<S>,
39}
40
41impl<S> IncomingCall<S> {
42    pub(crate) fn request(
43        method: String,
44        request_id: RequestId,
45        params: Value,
46        context: Context,
47        state: Arc<S>,
48    ) -> Self {
49        Self {
50            kind: CallKind::Request,
51            method,
52            request_id: Some(request_id),
53            params,
54            context,
55            state,
56        }
57    }
58
59    pub(crate) fn notification(
60        method: String,
61        params: Value,
62        context: Context,
63        state: Arc<S>,
64    ) -> Self {
65        Self {
66            kind: CallKind::Notification,
67            method,
68            request_id: None,
69            params,
70            context,
71            state,
72        }
73    }
74
75    /// Whether this call is a request or notification.
76    pub fn kind(&self) -> CallKind {
77        self.kind
78    }
79
80    /// The LSP or custom method name.
81    pub fn method(&self) -> &str {
82        &self.method
83    }
84
85    /// The JSON-RPC request ID, or `None` for a notification.
86    pub fn request_id(&self) -> Option<&RequestId> {
87        self.request_id.as_ref()
88    }
89
90    /// The decoded JSON parameters.
91    pub fn params(&self) -> &Value {
92        &self.params
93    }
94
95    /// Mutably borrow the decoded parameters before forwarding the call.
96    pub fn params_mut(&mut self) -> &mut Value {
97        &mut self.params
98    }
99
100    /// The framework context for this call.
101    pub fn context(&self) -> &Context {
102        &self.context
103    }
104
105    /// The connection's shared application state.
106    pub fn state(&self) -> &Arc<S> {
107        &self.state
108    }
109}
110
111/// The only outcomes of normalized user dispatch.
112pub enum ServiceResult {
113    /// A successful request result.
114    Response(Value),
115    /// A request error.
116    Error(LspError),
117    /// No response, as required for notifications.
118    NoResponse,
119}
120
121/// Boxed future returned by a user [`Layer`].
122pub type ServiceFuture = Pin<Box<dyn TaskFuture<ServiceResult> + 'static>>;
123
124macro_rules! service_trait {
125    ($($native_bound:tt)*) => {
126        pub(crate) trait Service<S>: TaskSend $($native_bound)* {
127            fn call(&self, call: IncomingCall<S>) -> ServiceFuture;
128        }
129    };
130}
131
132#[cfg(not(target_arch = "wasm32"))]
133service_trait!(+ Sync);
134#[cfg(target_arch = "wasm32")]
135service_trait!();
136
137/// The next inner Service in a user Layer chain.
138pub struct Next<S> {
139    inner: Arc<dyn Service<S>>,
140}
141
142impl<S> Clone for Next<S> {
143    fn clone(&self) -> Self {
144        Self {
145            inner: Arc::clone(&self.inner),
146        }
147    }
148}
149
150impl<S: Send + Sync + 'static> Next<S> {
151    /// Forward `call` to the next inner Layer or the Router.
152    pub fn call(&self, call: IncomingCall<S>) -> ServiceFuture {
153        self.inner.call(call)
154    }
155}
156
157macro_rules! layer_trait {
158    ($($native_bound:tt)*) => {
159        /// Adds cross-cutting behavior around normalized user dispatch.
160        ///
161        /// The last Layer registered on [`ServerBuilder`](crate::ServerBuilder) is
162        /// outermost among user Layers. Framework panic isolation, tracing, and
163        /// concurrency limiting always remain outside every user Layer.
164        pub trait Layer<S>: TaskSend $($native_bound)* + 'static {
165            /// Process `call`, optionally forwarding it through `next`.
166            fn call(&self, call: IncomingCall<S>, next: Next<S>) -> ServiceFuture;
167        }
168    };
169}
170
171#[cfg(not(target_arch = "wasm32"))]
172layer_trait!(+ Sync);
173#[cfg(target_arch = "wasm32")]
174layer_trait!();
175
176pub(crate) type UserLayer<S> = Arc<dyn Layer<S>>;
177pub(crate) type UserService<S> = Arc<dyn Service<S>>;
178
179struct LayerService<S> {
180    layer: UserLayer<S>,
181    inner: UserService<S>,
182}
183
184impl<S: Send + Sync + 'static> Service<S> for LayerService<S> {
185    fn call(&self, call: IncomingCall<S>) -> ServiceFuture {
186        self.layer.call(
187            call,
188            Next {
189                inner: Arc::clone(&self.inner),
190            },
191        )
192    }
193}
194
195struct RouterService<S> {
196    router: Arc<Router<S>>,
197}
198
199impl<S: Send + Sync + 'static> Service<S> for RouterService<S> {
200    fn call(&self, call: IncomingCall<S>) -> ServiceFuture {
201        let router = Arc::clone(&self.router);
202        Box::pin(async move {
203            match call.kind {
204                CallKind::Request => {
205                    let cancellation = call
206                        .context
207                        .cancellation()
208                        .cloned()
209                        .expect("request contexts carry cancellation");
210                    let result = if call.method == "workspace/executeCommand"
211                        && router.has_commands()
212                    {
213                        let params: lsp_types::ExecuteCommandParams =
214                            match serde_json::from_value(call.params) {
215                                Ok(params) => params,
216                                Err(error) => {
217                                    return ServiceResult::Error(LspError::invalid_params(error));
218                                }
219                            };
220                        match router.command(&params.command) {
221                            Some(handler) => {
222                                handler
223                                    .invoke((
224                                        call.state,
225                                        call.context,
226                                        params.arguments,
227                                        cancellation,
228                                    ))
229                                    .await
230                            }
231                            None => Err(LspError::invalid_params(format!(
232                                "unknown command: {}",
233                                params.command
234                            ))),
235                        }
236                    } else {
237                        match router.request(&call.method) {
238                            Some(handler) => {
239                                handler
240                                    .invoke((call.state, call.context, call.params, cancellation))
241                                    .await
242                            }
243                            None => Err(LspError::MethodNotFound(call.method)),
244                        }
245                    };
246                    match result {
247                        Ok(value) => ServiceResult::Response(value),
248                        Err(error) => ServiceResult::Error(error),
249                    }
250                }
251                CallKind::Notification => {
252                    // A built-in document notification reaches the stack only
253                    // once the engine has decoded and mutated; its hook lives
254                    // in a table registration keeps disjoint from the routes
255                    // (ADR 0018), so at most one of these two lookups can ever
256                    // match and their order carries no meaning.
257                    let handler = router
258                        .notification(&call.method)
259                        .or_else(|| router.built_in_hook(&call.method));
260                    if let Some(handler) = handler {
261                        handler
262                            .invoke((call.state, call.context, call.params))
263                            .await;
264                    }
265                    ServiceResult::NoResponse
266                }
267            }
268        })
269    }
270}
271
272struct ConcurrencyLimitService<S> {
273    inner: UserService<S>,
274    permits: Arc<Semaphore>,
275}
276
277impl<S: Send + Sync + 'static> Service<S> for ConcurrencyLimitService<S> {
278    fn call(&self, call: IncomingCall<S>) -> ServiceFuture {
279        let inner = Arc::clone(&self.inner);
280        let permits = Arc::clone(&self.permits);
281        Box::pin(async move {
282            let _permit = permits
283                .clone()
284                .acquire_owned()
285                .instrument(info_span!("handler.acquire_permit"))
286                .await;
287            inner.call(call).await
288        })
289    }
290}
291
292struct TracingService<S> {
293    inner: UserService<S>,
294}
295
296impl<S: Send + Sync + 'static> Service<S> for TracingService<S> {
297    fn call(&self, call: IncomingCall<S>) -> ServiceFuture {
298        let inner = Arc::clone(&self.inner);
299        let span = call.context.span().clone();
300        Box::pin(inner.call(call).instrument(span))
301    }
302}
303
304struct PanicIsolationService<S> {
305    inner: UserService<S>,
306}
307
308impl<S: Send + Sync + 'static> Service<S> for PanicIsolationService<S> {
309    fn call(&self, call: IncomingCall<S>) -> ServiceFuture {
310        let inner = Arc::clone(&self.inner);
311        let kind = call.kind;
312        Box::pin(async move {
313            match AssertUnwindSafe(async move { inner.call(call).await })
314                .catch_unwind()
315                .await
316            {
317                Ok(result) => result,
318                Err(_) if kind == CallKind::Request => {
319                    error!("panic isolated while dispatching request");
320                    ServiceResult::Error(LspError::internal("user dispatch panicked"))
321                }
322                Err(_) => {
323                    error!("panic isolated while dispatching notification");
324                    ServiceResult::NoResponse
325                }
326            }
327        })
328    }
329}
330
331pub(crate) fn build_service_stack<S>(
332    router: Arc<Router<S>>,
333    layers: Vec<UserLayer<S>>,
334    concurrency_limit: usize,
335) -> UserService<S>
336where
337    S: Send + Sync + 'static,
338{
339    let mut service: UserService<S> = Arc::new(RouterService { router });
340    for layer in layers {
341        service = Arc::new(LayerService {
342            layer,
343            inner: service,
344        });
345    }
346    service = Arc::new(ConcurrencyLimitService {
347        inner: service,
348        permits: Semaphore::shared(concurrency_limit),
349    });
350    service = Arc::new(TracingService { inner: service });
351    Arc::new(PanicIsolationService { inner: service })
352}