intelli_shell/service/
completion.rs1use std::sync::LazyLock;
2
3use regex::Regex;
4use tracing::instrument;
5use uuid::Uuid;
6
7use super::IntelliShellService;
8use crate::{
9 errors::{Result, UserFacingError},
10 model::VariableCompletion,
11 utils::{flatten_str, flatten_variable_name},
12};
13
14pub static FORBIDDEN_COMPLETION_ROOT_CMD_CHARS: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"[^\w-]").unwrap());
15pub static FORBIDDEN_COMPLETION_VARIABLE_CHARS: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"[:|{}]").unwrap());
16
17impl IntelliShellService {
18 #[instrument(skip_all)]
20 pub async fn list_variable_completion_root_cmds(&self) -> Result<Vec<String>> {
21 tracing::debug!("Listing variable completion root commands");
22 self.storage.list_variable_completion_root_cmds().await
23 }
24
25 #[instrument(skip_all)]
27 pub async fn list_variable_completions(&self, root_cmd: Option<&str>) -> Result<Vec<VariableCompletion>> {
28 tracing::debug!("Listing variable completions for '{:?}'", root_cmd);
29
30 let flat_root_cmd = root_cmd.map(flatten_str);
31
32 self.storage.list_variable_completions(flat_root_cmd, None, false).await
33 }
34
35 #[instrument(skip_all)]
37 pub async fn create_variable_completion(&self, var: VariableCompletion) -> Result<VariableCompletion> {
38 if var.flat_variable.is_empty() {
39 return Err(UserFacingError::CompletionEmptyVariable.into());
40 }
41 if var.suggestions_provider.is_empty() {
42 return Err(UserFacingError::CompletionEmptySuggestionsProvider.into());
43 }
44 if FORBIDDEN_COMPLETION_ROOT_CMD_CHARS.is_match(&var.root_cmd) {
45 return Err(UserFacingError::CompletionInvalidCommand.into());
46 }
47 if FORBIDDEN_COMPLETION_VARIABLE_CHARS.is_match(&var.variable) {
48 return Err(UserFacingError::CompletionInvalidVariable.into());
49 }
50
51 if var.is_global() {
52 tracing::info!(
53 "Creating a global variable completion for '{}': {}",
54 var.flat_variable,
55 var.suggestions_provider
56 );
57 } else {
58 tracing::info!(
59 "Creating a variable completion for '{}' '{}': {}",
60 var.flat_root_cmd,
61 var.flat_variable,
62 var.suggestions_provider
63 );
64 }
65
66 self.storage.insert_variable_completion(var).await
67 }
68
69 #[instrument(skip_all)]
71 pub async fn update_variable_completion(&self, var: VariableCompletion) -> Result<VariableCompletion> {
72 if var.flat_variable.is_empty() {
73 return Err(UserFacingError::CompletionEmptyVariable.into());
74 }
75 if var.suggestions_provider.is_empty() {
76 return Err(UserFacingError::CompletionEmptySuggestionsProvider.into());
77 }
78 if FORBIDDEN_COMPLETION_ROOT_CMD_CHARS.is_match(&var.root_cmd) {
79 return Err(UserFacingError::CompletionInvalidCommand.into());
80 }
81 if FORBIDDEN_COMPLETION_VARIABLE_CHARS.is_match(&var.variable) {
82 return Err(UserFacingError::CompletionInvalidVariable.into());
83 }
84
85 tracing::info!(
86 "Updating variable completion '{}': {}",
87 var.id,
88 var.suggestions_provider
89 );
90
91 self.storage.update_variable_completion(var).await
92 }
93
94 #[instrument(skip_all)]
96 pub async fn delete_variable_completion(&self, id: Uuid) -> Result<()> {
97 tracing::info!("Deleting variable completion: {id}");
98 self.storage.delete_variable_completion(id).await
99 }
100
101 #[instrument(skip_all)]
103 pub async fn delete_variable_completion_by_key(
104 &self,
105 root_cmd: impl AsRef<str>,
106 variable_name: impl AsRef<str>,
107 ) -> Result<Option<VariableCompletion>> {
108 let flat_root_cmd = flatten_str(root_cmd);
109 let flat_variable_name = flatten_variable_name(variable_name);
110
111 if flat_root_cmd.is_empty() {
112 tracing::info!("Deleting global variable completion for '{flat_variable_name}'");
113 } else {
114 tracing::info!("Deleting variable completion for '{flat_root_cmd}' '{flat_variable_name}'");
115 }
116
117 self.storage
118 .delete_variable_completion_by_key(flat_root_cmd, flat_variable_name)
119 .await
120 }
121}