Skip to main content

qlue_ls/server/
mod.rs

1//! Core language server implementation.
2//!
3//! This module contains the [`Server`] struct and the main message handling loop.
4//! The server is platform-agnostic and works with both native stdio and WASM streams.
5//!
6//! # Key Types
7//!
8//! - [`Server`]: Holds server state, settings, capabilities, and the message send function
9//! - [`handle_message`]: Entry point for processing incoming LSP messages
10//!
11//! # Architecture
12//!
13//! The server uses a closure-based approach for output: callers provide a `Fn(String)`
14//! that handles sending responses. This allows the same server code to work with
15//! stdio (native) or Web Streams (WASM).
16//!
17//! Message dispatch happens in [`message_handler`], which routes method names to
18//! specific handlers. The server is wrapped in `Rc<Mutex<>>` for async access.
19//!
20//! # Submodules
21//!
22//! - [`state`]: Document storage, parse tree cache, backend registry
23//! - [`capabilities`]: LSP capability negotiation
24//! - [`configuration`]: Settings from `qlue-ls.toml`/`qlue-ls.yml`
25//! - [`message_handler`]: Request/notification dispatch
26//! - [`lsp`]: Protocol types and JSON-RPC serialization
27//! - [`analysis`]: Semantic analysis (completions, hover, etc.)
28
29mod analysis;
30mod capabilities;
31mod common;
32pub(crate) mod configuration;
33mod lsp;
34mod sparql_operations;
35mod state;
36mod tools;
37
38pub(crate) mod message_handler;
39
40use capabilities::create_capabilities;
41use configuration::Settings;
42use futures::lock::Mutex;
43use log::{error, info};
44use lsp::{
45    ServerInfo,
46    errors::{ErrorCode, LSPError},
47    rpc::{RecoverId, ResponseMessage},
48};
49use message_handler::dispatch;
50use serde::Serialize;
51use state::ServerState;
52use std::{any::type_name, collections::HashMap, fmt::Debug, rc::Rc};
53use tools::Tools;
54use wasm_bindgen::prelude::wasm_bindgen;
55
56use crate::server::{configuration::CompletionTemplate, lsp::LspMessage};
57
58#[wasm_bindgen]
59pub struct Server {
60    pub(crate) state: ServerState,
61    pub(crate) settings: Settings,
62    pub(crate) capabilities: lsp::capabilities::ServerCapabilities,
63    pub(crate) client_capabilities: Option<lsp::capabilities::ClientCapabilities>,
64    pub(crate) server_info: ServerInfo,
65    tools: Tools,
66    send_message_closure: Box<dyn Fn(String)>,
67}
68
69impl Server {
70    pub fn new(write_function: impl Fn(String) + 'static) -> Server {
71        let version = env!("CARGO_PKG_VERSION");
72        info!("Started Language Server: Qlue-ls - version: {}", version);
73        Self {
74            state: ServerState::new(),
75            settings: Settings::new(),
76            capabilities: create_capabilities(),
77            client_capabilities: None,
78            server_info: ServerInfo {
79                name: "Qlue-ls".to_string(),
80                version: Some(version.to_string()),
81            },
82            tools: Tools::init(),
83            send_message_closure: Box::new(write_function),
84        }
85    }
86
87    pub(crate) fn bump_request_id(&mut self) -> u32 {
88        self.state.bump_request_id()
89    }
90
91    pub fn get_version(&self) -> String {
92        self.server_info
93            .version
94            .clone()
95            .unwrap_or("not-specified".to_string())
96    }
97
98    fn send_message<T>(&self, message: T) -> Result<(), LSPError>
99    where
100        T: Serialize + LspMessage + Debug,
101    {
102        let message_string = serde_json::to_string(&message).map_err(|error| {
103            LSPError::new(
104                ErrorCode::ParseError,
105                &format!(
106                    "Could not deserialize RPC-message \"{}\"\n\n{}",
107                    type_name::<T>(),
108                    error
109                ),
110            )
111        })?;
112        (self.send_message_closure)(message_string);
113        Ok(())
114    }
115
116    /// Shortens a raw URI into its CURIE (Compact URI) form and retrieves related metadata.
117    ///
118    /// This method takes a raw URI as input, looks up a URI converter from the given backend
119    /// (or falls back to the default converter), and attempts to compress the URI into its
120    /// CURIE form.
121    ///
122    /// # Parameters
123    /// - `uri`: A string slice representing the raw URI to be shortened.
124    /// - `backend_name`: An optional backend name whose converter should be used.
125    ///   If `None` or if the backend is not found, the default converter is used.
126    ///
127    /// # Returns
128    /// - `Some((prefix, uri_prefix, curie))` if the URI can be successfully compacted:
129    ///   - `prefix`: A `String` representing the prefix associated with the URI.
130    ///   - `uri_prefix`: A `String` representing the URI namespace prefix.
131    ///   - `curie`: A `String` representing the compact CURIE form of the URI.
132    /// - `None` if no converter is available, or the URI cannot be found or shortened.
133    pub(crate) fn shorten_uri(
134        &self,
135        uri: &str,
136        backend_name: Option<&str>,
137    ) -> Option<(String, String, String)> {
138        let converter = backend_name
139            .and_then(|name| self.state.get_converter(name))
140            .or(self.state.get_default_converter())?;
141        let record = converter.find_by_uri(uri).ok()?;
142        let curie = converter.compress(uri).ok()?;
143        Some((record.prefix.clone(), record.uri_prefix.clone(), curie))
144    }
145
146    pub(crate) fn load_templates(
147        &mut self,
148        backend_name: &str,
149        templates: HashMap<CompletionTemplate, String>,
150    ) -> Result<(), LSPError> {
151        for (key, value) in templates {
152            self.tools
153                .tera
154                .add_raw_template(&format!("{}-{}", &backend_name, &key), &value)
155                .map_err(|err| {
156                    log::error!("{}", err);
157                    LSPError::new(
158                        ErrorCode::InvalidParams,
159                        &format!(
160                            "Could not load template: {} of backend {}",
161                            &key, &backend_name
162                        ),
163                    )
164                })?;
165        }
166        Ok(())
167    }
168}
169
170async fn handle_error(server_rc: Rc<Mutex<Server>>, message: &str, error: LSPError) {
171    log::error!(
172        "Error occurred while handling message:\n\"{}\"\n\n{:?}\n{}",
173        message,
174        error.code,
175        error.message
176    );
177    if let Ok(id) = serde_json::from_str::<RecoverId>(message).map(|msg| msg.id)
178        && let Err(error) = server_rc
179            .lock()
180            .await
181            .send_message(ResponseMessage::error(&id, error))
182    {
183        error!(
184            "CRITICAL: could not serialize error message (this very bad):\n{:?}",
185            error
186        )
187    }
188}
189
190pub async fn handle_message(server_rc: Rc<Mutex<Server>>, message: String) {
191    if let Err(err) = dispatch(server_rc.clone(), &message).await {
192        handle_error(server_rc.clone(), &message, err).await;
193    }
194}