Skip to main content

corsa_client/api/
capabilities.rs

1use corsa_core::fast::CompactString;
2use serde::{Deserialize, Serialize};
3
4/// Runtime capability summary returned by `describeCapabilities`.
5#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
6#[serde(rename_all = "camelCase")]
7pub struct CapabilitiesResponse {
8    /// Runtime identity and transport metadata.
9    #[serde(default)]
10    pub runtime: RuntimeCapabilities,
11    /// Overlay-related feature flags.
12    #[serde(default)]
13    pub overlay: OverlayCapabilities,
14    /// Diagnostics API availability by scope.
15    #[serde(default)]
16    pub diagnostics: DiagnosticsCapabilities,
17    /// Editor-style API availability by feature.
18    #[serde(default)]
19    pub editor: EditorCapabilities,
20}
21
22/// Runtime identity details for the active worker.
23#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
24#[serde(rename_all = "camelCase")]
25pub struct RuntimeCapabilities {
26    /// Human-oriented runtime kind such as `tsgo`, `native-preview`, or `mock-tsgo`.
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub kind: Option<CompactString>,
29    /// Executable path used to spawn the worker when known locally.
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub executable: Option<CompactString>,
32    /// Transport identifier such as `jsonrpc` or `msgpack`.
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub transport: Option<CompactString>,
35    /// Whether the runtime implemented the `describeCapabilities` endpoint.
36    #[serde(default)]
37    pub capability_endpoint: bool,
38}
39
40/// Overlay support exposed by the runtime.
41#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
42#[serde(rename_all = "camelCase")]
43pub struct OverlayCapabilities {
44    /// Whether `updateSnapshot` accepts `overlayChanges`.
45    #[serde(default)]
46    pub update_snapshot_overlay_changes: bool,
47}
48
49/// Diagnostics API support grouped by scope.
50#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
51#[serde(rename_all = "camelCase")]
52pub struct DiagnosticsCapabilities {
53    /// Whether snapshot-wide diagnostics are available.
54    #[serde(default)]
55    pub snapshot: bool,
56    /// Whether project-wide diagnostics are available.
57    #[serde(default)]
58    pub project: bool,
59    /// Whether file-scoped diagnostics are available.
60    #[serde(default)]
61    pub file: bool,
62}
63
64/// Editor API support grouped by feature.
65#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
66#[serde(rename_all = "camelCase")]
67pub struct EditorCapabilities {
68    /// Whether hover information is available.
69    #[serde(default)]
70    pub hover: bool,
71    /// Whether definition lookup is available.
72    #[serde(default)]
73    pub definition: bool,
74    /// Whether reference lookup is available.
75    #[serde(default)]
76    pub references: bool,
77    /// Whether rename edits are available.
78    #[serde(default)]
79    pub rename: bool,
80    /// Whether completion items are available.
81    #[serde(default)]
82    pub completion: bool,
83}
84
85impl CapabilitiesResponse {
86    pub(crate) fn fallback(runtime: RuntimeCapabilities) -> Self {
87        Self {
88            runtime,
89            overlay: OverlayCapabilities::default(),
90            diagnostics: DiagnosticsCapabilities::default(),
91            editor: EditorCapabilities::default(),
92        }
93    }
94}
95
96impl RuntimeCapabilities {
97    pub(crate) fn merge_with_local(mut self, local: RuntimeCapabilities) -> Self {
98        if self.kind.is_none() {
99            self.kind = local.kind;
100        }
101        if self.executable.is_none() {
102            self.executable = local.executable;
103        }
104        if self.transport.is_none() {
105            self.transport = local.transport;
106        }
107        self.capability_endpoint |= local.capability_endpoint;
108        self
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::{CapabilitiesResponse, RuntimeCapabilities};
115    use corsa_core::fast::CompactString;
116
117    #[test]
118    fn fallback_keeps_runtime_identity_and_disables_features() {
119        let response = CapabilitiesResponse::fallback(RuntimeCapabilities {
120            kind: Some(CompactString::from("tsgo")),
121            executable: Some(CompactString::from("/tmp/tsgo")),
122            transport: Some(CompactString::from("msgpack")),
123            capability_endpoint: false,
124        });
125
126        assert_eq!(response.runtime.kind.as_deref(), Some("tsgo"));
127        assert!(!response.overlay.update_snapshot_overlay_changes);
128        assert!(!response.diagnostics.snapshot);
129        assert!(!response.editor.hover);
130    }
131}