Skip to content

Commit 0b2ea71

Browse files
committed
refactor: extract lsp server module
1 parent bc3cc5c commit 0b2ea71

File tree

3 files changed

+244
-138
lines changed

3 files changed

+244
-138
lines changed

src/app/lsp_server.rs renamed to src/app/lsp_server/mod.rs

Lines changed: 52 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@ use std::borrow::Cow;
22
use std::path::PathBuf;
33
use std::str::FromStr;
44

5-
use serde_json::{Value, json};
5+
use serde_json::Value;
66
use tokio::sync::RwLock;
77
use tower_lsp::LanguageServer;
88
use tower_lsp::jsonrpc::{Error, ErrorCode, Result};
99
use tower_lsp::lsp_types::{
1010
CodeActionOrCommand, CodeActionParams, CodeActionProviderCapability, CodeActionResponse,
1111
CodeLens, CodeLensOptions, CodeLensParams, Command, DidChangeConfigurationParams,
1212
DidChangeTextDocumentParams, DidOpenTextDocumentParams, ExecuteCommandOptions,
13-
ExecuteCommandParams, InitializeParams, InitializeResult, InitializedParams, MessageType,
14-
Range, ServerCapabilities, TextDocumentSyncCapability, TextDocumentSyncKind,
13+
ExecuteCommandParams, InitializeParams, InitializeResult, InitializedParams, Location,
14+
MessageType, Range, ServerCapabilities, TextDocumentSyncCapability, TextDocumentSyncKind,
1515
};
1616
use tracing::{debug, info};
1717

@@ -21,6 +21,9 @@ use super::queries::QueryExecutor;
2121
use super::{InMemoryDocumentDatabase, LSPClient};
2222
use crate::infra::{parse_compose_file, parse_dockerfile};
2323

24+
mod supported_commands;
25+
use supported_commands::SupportedCommands;
26+
2427
pub struct LSPServer<C> {
2528
command_executor: CommandExecutor<C>,
2629
query_executor: QueryExecutor,
@@ -58,32 +61,6 @@ where
5861
}
5962
}
6063

61-
pub enum SupportedCommands {
62-
ExecuteBaseImageScan,
63-
ExecuteBuildAndScan,
64-
}
65-
66-
impl std::fmt::Display for SupportedCommands {
67-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68-
f.write_str(match self {
69-
Self::ExecuteBaseImageScan => "sysdig-lsp.execute-scan",
70-
Self::ExecuteBuildAndScan => "sysdig-lsp.execute-build-and-scan",
71-
})
72-
}
73-
}
74-
75-
impl TryFrom<&str> for SupportedCommands {
76-
type Error = String;
77-
78-
fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
79-
match value {
80-
"sysdig-lsp.execute-scan" => Ok(SupportedCommands::ExecuteBaseImageScan),
81-
"sysdig-lsp.execute-build-and-scan" => Ok(SupportedCommands::ExecuteBuildAndScan),
82-
_ => Err(format!("command not supported: {value}")),
83-
}
84-
}
85-
}
86-
8764
struct CommandInfo {
8865
title: String,
8966
command: String,
@@ -115,22 +92,19 @@ where
11592

11693
fn generate_compose_commands(
11794
&self,
118-
uri: &tower_lsp::lsp_types::Url,
95+
url: &tower_lsp::lsp_types::Url,
11996
content: &str,
12097
) -> Vec<CommandInfo> {
12198
let mut commands = vec![];
12299
if let Ok(instructions) = parse_compose_file(content) {
123100
for instruction in instructions {
124-
commands.push(CommandInfo {
125-
title: "Scan base image".to_string(),
126-
command: SupportedCommands::ExecuteBaseImageScan.to_string(),
127-
arguments: Some(vec![
128-
json!(uri),
129-
json!(instruction.range),
130-
json!(instruction.image_name),
131-
]),
132-
range: instruction.range,
133-
});
101+
commands.push(
102+
SupportedCommands::ExecuteBaseImageScan {
103+
location: Location::new(url.clone(), instruction.range),
104+
image: instruction.image_name,
105+
}
106+
.into(),
107+
);
134108
}
135109
}
136110
commands
@@ -149,21 +123,20 @@ where
149123
.next_back()
150124
{
151125
let range = last_from_instruction.range;
152-
let line = last_from_instruction.range.start.line;
153-
commands.push(CommandInfo {
154-
title: "Build and scan".to_string(),
155-
command: SupportedCommands::ExecuteBuildAndScan.to_string(),
156-
arguments: Some(vec![json!(uri), json!(line)]),
157-
range,
158-
});
159-
160-
if let Some(image_name) = last_from_instruction.arguments.first() {
161-
commands.push(CommandInfo {
162-
title: "Scan base image".to_string(),
163-
command: SupportedCommands::ExecuteBaseImageScan.to_string(),
164-
arguments: Some(vec![json!(uri), json!(range), json!(image_name)]),
165-
range,
166-
});
126+
commands.push(
127+
SupportedCommands::ExecuteBuildAndScan {
128+
location: Location::new(uri.clone(), range),
129+
}
130+
.into(),
131+
);
132+
if let Some(image) = last_from_instruction.arguments.first() {
133+
commands.push(
134+
SupportedCommands::ExecuteBaseImageScan {
135+
location: Location::new(uri.clone(), range),
136+
image: image.to_owned(),
137+
}
138+
.into(),
139+
);
167140
}
168141
}
169142
commands
@@ -196,10 +169,7 @@ where
196169
resolve_provider: Some(false),
197170
}),
198171
execute_command_provider: Some(ExecuteCommandOptions {
199-
commands: vec![
200-
SupportedCommands::ExecuteBaseImageScan.to_string(),
201-
SupportedCommands::ExecuteBuildAndScan.to_string(),
202-
],
172+
commands: SupportedCommands::all_supported_commands_as_string(),
203173
..Default::default()
204174
}),
205175
..Default::default()
@@ -296,20 +266,25 @@ where
296266
}
297267

298268
async fn execute_command(&self, params: ExecuteCommandParams) -> Result<Option<Value>> {
299-
let command: SupportedCommands = params.command.as_str().try_into().map_err(|e| {
300-
Error::internal_error().with_message(format!("unable to parse command: {e}"))
301-
})?;
269+
let command: SupportedCommands = params.try_into()?;
270+
271+
let result = match command.clone() {
272+
SupportedCommands::ExecuteBaseImageScan { location, image } => {
273+
execute_command_scan_base_image(
274+
self,
275+
location.uri.to_string(),
276+
location.range,
277+
image,
278+
)
279+
.await
280+
.map(|_| None)
281+
}
302282

303-
let result = match command {
304-
SupportedCommands::ExecuteBaseImageScan => {
305-
execute_command_scan_base_image(self, &params)
283+
SupportedCommands::ExecuteBuildAndScan { location } => {
284+
execute_command_build_and_scan(self, location.uri.to_string(), location.range)
306285
.await
307286
.map(|_| None)
308287
}
309-
310-
SupportedCommands::ExecuteBuildAndScan => execute_command_build_and_scan(self, &params)
311-
.await
312-
.map(|_| None),
313288
};
314289

315290
match result {
@@ -331,32 +306,10 @@ where
331306

332307
async fn execute_command_scan_base_image<C: LSPClient>(
333308
server: &LSPServer<C>,
334-
params: &ExecuteCommandParams,
309+
file: String,
310+
range: Range,
311+
image: String,
335312
) -> Result<()> {
336-
let Some(uri) = params.arguments.first() else {
337-
return Err(Error::internal_error().with_message("no uri was provided"));
338-
};
339-
340-
let Some(uri) = uri.as_str() else {
341-
return Err(Error::internal_error().with_message("uri is not a string"));
342-
};
343-
344-
let Some(range) = params.arguments.get(1) else {
345-
return Err(Error::internal_error().with_message("no range was provided"));
346-
};
347-
348-
let Ok(range) = serde_json::from_value::<Range>(range.clone()) else {
349-
return Err(Error::internal_error().with_message("range is not a Range object"));
350-
};
351-
352-
let Some(image_name) = params.arguments.get(2) else {
353-
return Err(Error::internal_error().with_message("no image name was provided"));
354-
};
355-
356-
let Some(image_name) = image_name.as_str() else {
357-
return Err(Error::internal_error().with_message("image name is not a string"));
358-
};
359-
360313
let image_scanner = {
361314
let mut lock = server.component_factory.write().await;
362315
lock.image_scanner().map_err(|e| {
@@ -366,32 +319,17 @@ async fn execute_command_scan_base_image<C: LSPClient>(
366319

367320
server
368321
.command_executor
369-
.scan_image(uri, range, image_name, &image_scanner)
322+
.scan_image(&file, range, &image, &image_scanner)
370323
.await?;
371324

372325
Ok(())
373326
}
374327

375328
async fn execute_command_build_and_scan<C: LSPClient>(
376329
server: &LSPServer<C>,
377-
params: &ExecuteCommandParams,
330+
file: String,
331+
range: Range,
378332
) -> Result<()> {
379-
let Some(uri) = params.arguments.first() else {
380-
return Err(Error::internal_error().with_message("no uri was provided"));
381-
};
382-
383-
let Some(uri) = uri.as_str() else {
384-
return Err(Error::internal_error().with_message("uri is not a string"));
385-
};
386-
387-
let Some(line) = params.arguments.get(1) else {
388-
return Err(Error::internal_error().with_message("no line was provided"));
389-
};
390-
391-
let Some(line) = line.as_u64().and_then(|x| u32::try_from(x).ok()) else {
392-
return Err(Error::internal_error().with_message("line is not a u32"));
393-
};
394-
395333
let (image_scanner, image_builder) = {
396334
let mut factory = server.component_factory.write().await;
397335

@@ -408,8 +346,8 @@ async fn execute_command_build_and_scan<C: LSPClient>(
408346
server
409347
.command_executor
410348
.build_and_scan_from_file(
411-
&PathBuf::from_str(uri).unwrap(),
412-
line,
349+
&PathBuf::from_str(&file).unwrap(),
350+
range.start.line,
413351
&image_builder,
414352
&image_scanner,
415353
)
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
use std::fmt::Display;
2+
3+
use super::CommandInfo;
4+
use serde_json::json;
5+
use tower_lsp::{
6+
jsonrpc::{self, Error},
7+
lsp_types::{ExecuteCommandParams, Location},
8+
};
9+
10+
const CMD_EXECUTE_SCAN: &str = "sysdig-lsp.execute-scan";
11+
const CMD_BUILD_AND_SCAN: &str = "sysdig-lsp.execute-build-and-scan";
12+
13+
#[derive(Debug, Clone)]
14+
pub enum SupportedCommands {
15+
ExecuteBaseImageScan { location: Location, image: String },
16+
ExecuteBuildAndScan { location: Location },
17+
}
18+
19+
impl SupportedCommands {
20+
fn as_string_command(&self) -> String {
21+
match self {
22+
SupportedCommands::ExecuteBaseImageScan { .. } => CMD_EXECUTE_SCAN,
23+
SupportedCommands::ExecuteBuildAndScan { .. } => CMD_BUILD_AND_SCAN,
24+
}
25+
.to_string()
26+
}
27+
28+
pub fn all_supported_commands_as_string() -> Vec<String> {
29+
[CMD_EXECUTE_SCAN, CMD_BUILD_AND_SCAN]
30+
.into_iter()
31+
.map(|s| s.to_string())
32+
.collect()
33+
}
34+
}
35+
36+
impl From<SupportedCommands> for CommandInfo {
37+
fn from(value: SupportedCommands) -> Self {
38+
match &value {
39+
SupportedCommands::ExecuteBaseImageScan { location, image } => CommandInfo {
40+
title: "Scan base image".to_owned(),
41+
command: value.as_string_command(),
42+
arguments: Some(vec![json!(location), json!(image)]),
43+
range: location.range,
44+
},
45+
46+
SupportedCommands::ExecuteBuildAndScan { location } => CommandInfo {
47+
title: "Build and scan".to_owned(),
48+
command: value.as_string_command(),
49+
arguments: Some(vec![json!(location)]),
50+
range: location.range,
51+
},
52+
}
53+
}
54+
}
55+
56+
impl TryFrom<ExecuteCommandParams> for SupportedCommands {
57+
type Error = jsonrpc::Error;
58+
59+
fn try_from(value: ExecuteCommandParams) -> std::result::Result<Self, Self::Error> {
60+
match (value.command.as_str(), value.arguments.as_slice()) {
61+
(CMD_EXECUTE_SCAN, [location, image]) => Ok(SupportedCommands::ExecuteBaseImageScan {
62+
location: serde_json::from_value(location.clone())
63+
.map_err(|_| Error::invalid_params("location must be a Location object"))?,
64+
image: image
65+
.as_str()
66+
.ok_or_else(|| Error::invalid_params("image must be string"))?
67+
.to_owned(),
68+
}),
69+
(CMD_BUILD_AND_SCAN, [location]) => Ok(SupportedCommands::ExecuteBuildAndScan {
70+
location: serde_json::from_value(location.clone())
71+
.map_err(|_| Error::invalid_params("location must be a Location object"))?,
72+
}),
73+
(other, _) => Err(Error::invalid_params(format!(
74+
"command not supported: {other}"
75+
))),
76+
}
77+
}
78+
}
79+
80+
impl Display for SupportedCommands {
81+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82+
match self {
83+
SupportedCommands::ExecuteBaseImageScan { location, image } => {
84+
write!(
85+
f,
86+
"ExecuteBaseImageScan(location: {location:?}, image: {image})",
87+
)
88+
}
89+
SupportedCommands::ExecuteBuildAndScan { location } => {
90+
write!(f, "ExecuteBuildAndScan(location: {location:?})")
91+
}
92+
}
93+
}
94+
}

0 commit comments

Comments
 (0)