ricecoder_external_lsp/mapping/
transformer.rs1use crate::error::{ExternalLspError, Result};
7use crate::types::{CompletionMappingRules, DiagnosticsMappingRules, HoverMappingRules};
8use serde_json::{json, Value};
9use std::collections::HashMap;
10
11use super::json_path::JsonPathParser;
12
13#[derive(Debug, Clone)]
15pub struct OutputTransformer {
16 custom_transforms: HashMap<String, String>,
18}
19
20impl OutputTransformer {
21 pub fn new() -> Self {
23 Self {
24 custom_transforms: HashMap::new(),
25 }
26 }
27
28 pub fn with_transforms(custom_transforms: HashMap<String, String>) -> Self {
30 Self { custom_transforms }
31 }
32
33 pub fn register_transform(&mut self, name: String, function: String) {
35 self.custom_transforms.insert(name, function);
36 }
37
38 pub fn transform_completion(
40 &self,
41 response: &Value,
42 rules: &CompletionMappingRules,
43 ) -> Result<Vec<Value>> {
44 let items_parser = JsonPathParser::parse(&rules.items_path)?;
46 let items = items_parser.extract(response)?;
47
48 if items.is_empty() {
49 return Ok(Vec::new());
50 }
51
52 let items_array = if items.len() == 1 && items[0].is_array() {
54 items[0].as_array().unwrap().clone()
55 } else if items.len() == 1 && items[0].is_object() {
56 vec![items[0].clone()]
58 } else {
59 items
61 };
62
63 let mut results = Vec::new();
65 for item in items_array {
66 let transformed = self.apply_field_mappings(&item, &rules.field_mappings)?;
67
68 let final_item = if let Some(transform_name) = &rules.transform {
70 self.apply_custom_transform(&transformed, transform_name)?
71 } else {
72 transformed
73 };
74
75 results.push(final_item);
76 }
77
78 Ok(results)
79 }
80
81 pub fn transform_diagnostics(
83 &self,
84 response: &Value,
85 rules: &DiagnosticsMappingRules,
86 ) -> Result<Vec<Value>> {
87 let items_parser = JsonPathParser::parse(&rules.items_path)?;
89 let items = items_parser.extract(response)?;
90
91 if items.is_empty() {
92 return Ok(Vec::new());
93 }
94
95 let items_array = if items.len() == 1 && items[0].is_array() {
97 items[0].as_array().unwrap().clone()
98 } else if items.len() == 1 && items[0].is_object() {
99 vec![items[0].clone()]
101 } else {
102 items
104 };
105
106 let mut results = Vec::new();
108 for item in items_array {
109 let transformed = self.apply_field_mappings(&item, &rules.field_mappings)?;
110
111 let final_item = if let Some(transform_name) = &rules.transform {
113 self.apply_custom_transform(&transformed, transform_name)?
114 } else {
115 transformed
116 };
117
118 results.push(final_item);
119 }
120
121 Ok(results)
122 }
123
124 pub fn transform_hover(
126 &self,
127 response: &Value,
128 rules: &HoverMappingRules,
129 ) -> Result<Value> {
130 let content_parser = JsonPathParser::parse(&rules.content_path)?;
132 let content = content_parser.extract_single(response)?;
133
134 let transformed = self.apply_field_mappings(&content, &rules.field_mappings)?;
136
137 let final_value = if let Some(transform_name) = &rules.transform {
139 self.apply_custom_transform(&transformed, transform_name)?
140 } else {
141 transformed
142 };
143
144 Ok(final_value)
145 }
146
147 fn apply_field_mappings(
149 &self,
150 source: &Value,
151 field_mappings: &HashMap<String, String>,
152 ) -> Result<Value> {
153 let mut result = json!({});
154
155 for (target_field, source_path) in field_mappings {
156 let parser = JsonPathParser::parse(source_path)?;
157
158 match parser.extract_single(source) {
159 Ok(value) => {
160 result[target_field] = value;
161 }
162 Err(_) => {
163 continue;
165 }
166 }
167 }
168
169 Ok(result)
170 }
171
172 fn apply_custom_transform(&self, value: &Value, transform_name: &str) -> Result<Value> {
174 match transform_name {
177 "identity" => Ok(value.clone()),
178 "stringify" => Ok(Value::String(value.to_string())),
179 _ => {
180 if self.custom_transforms.contains_key(transform_name) {
182 Ok(value.clone())
185 } else {
186 Err(ExternalLspError::TransformationError(format!(
187 "Unknown transformation function: {}",
188 transform_name
189 )))
190 }
191 }
192 }
193 }
194}
195
196impl Default for OutputTransformer {
197 fn default() -> Self {
198 Self::new()
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205
206 #[test]
207 fn test_transform_completion_simple() {
208 let transformer = OutputTransformer::new();
209 let response = json!({
210 "result": {
211 "items": [
212 {"label": "foo", "detail": "function"},
213 {"label": "bar", "detail": "variable"}
214 ]
215 }
216 });
217
218 let mut field_mappings = HashMap::new();
219 field_mappings.insert("label".to_string(), "$.label".to_string());
220 field_mappings.insert("detail".to_string(), "$.detail".to_string());
221
222 let rules = CompletionMappingRules {
223 items_path: "$.result.items".to_string(),
224 field_mappings,
225 transform: None,
226 };
227
228 let results = transformer.transform_completion(&response, &rules).unwrap();
229 assert_eq!(results.len(), 2);
230 assert_eq!(results[0]["label"], "foo");
231 assert_eq!(results[1]["label"], "bar");
232 }
233
234 #[test]
235 fn test_transform_completion_with_wildcard() {
236 let transformer = OutputTransformer::new();
237 let response = json!({
238 "completions": [
239 {"name": "foo", "type": "function"},
240 {"name": "bar", "type": "variable"}
241 ]
242 });
243
244 let mut field_mappings = HashMap::new();
245 field_mappings.insert("label".to_string(), "$.name".to_string());
246 field_mappings.insert("kind".to_string(), "$.type".to_string());
247
248 let rules = CompletionMappingRules {
249 items_path: "$.completions[*]".to_string(),
250 field_mappings,
251 transform: None,
252 };
253
254 let results = transformer.transform_completion(&response, &rules).unwrap();
255 assert_eq!(results.len(), 2);
256 assert_eq!(results[0]["label"], "foo");
257 assert_eq!(results[0]["kind"], "function");
258 }
259
260 #[test]
261 fn test_transform_diagnostics() {
262 let transformer = OutputTransformer::new();
263 let response = json!({
264 "issues": [
265 {"message": "error", "line": 1},
266 {"message": "warning", "line": 2}
267 ]
268 });
269
270 let mut field_mappings = HashMap::new();
271 field_mappings.insert("message".to_string(), "$.message".to_string());
272 field_mappings.insert("line".to_string(), "$.line".to_string());
273
274 let rules = DiagnosticsMappingRules {
275 items_path: "$.issues".to_string(),
276 field_mappings,
277 transform: None,
278 };
279
280 let results = transformer.transform_diagnostics(&response, &rules).unwrap();
281 assert_eq!(results.len(), 2);
282 assert_eq!(results[0]["message"], "error");
283 }
284
285 #[test]
286 fn test_transform_hover() {
287 let transformer = OutputTransformer::new();
288 let response = json!({
289 "result": {
290 "contents": {
291 "language": "rust",
292 "value": "fn foo() -> i32"
293 }
294 }
295 });
296
297 let mut field_mappings = HashMap::new();
298 field_mappings.insert("language".to_string(), "$.language".to_string());
299 field_mappings.insert("value".to_string(), "$.value".to_string());
300
301 let rules = HoverMappingRules {
302 content_path: "$.result.contents".to_string(),
303 field_mappings,
304 transform: None,
305 };
306
307 let result = transformer.transform_hover(&response, &rules).unwrap();
308 assert_eq!(result["language"], "rust");
309 assert_eq!(result["value"], "fn foo() -> i32");
310 }
311
312 #[test]
313 fn test_missing_field_in_mapping() {
314 let transformer = OutputTransformer::new();
315 let response = json!({
316 "result": {
317 "items": [
318 {"label": "foo"}
319 ]
320 }
321 });
322
323 let mut field_mappings = HashMap::new();
324 field_mappings.insert("label".to_string(), "$.label".to_string());
325 field_mappings.insert("detail".to_string(), "$.detail".to_string()); let rules = CompletionMappingRules {
328 items_path: "$.result.items".to_string(),
329 field_mappings,
330 transform: None,
331 };
332
333 let results = transformer.transform_completion(&response, &rules).unwrap();
334 assert_eq!(results.len(), 1);
335 assert_eq!(results[0]["label"], "foo");
336 assert!(!results[0].get("detail").is_some() || results[0]["detail"].is_null());
337 }
338}