ricecoder_external_lsp/mapping/
completion.rs

1//! Completion output mapping
2//!
3//! Maps LSP completion responses to ricecoder CompletionItem models.
4//! Supports custom field mappings via configuration and transformation functions.
5
6use crate::error::Result;
7use crate::types::CompletionMappingRules;
8use serde_json::Value;
9
10use super::transformer::OutputTransformer;
11
12/// Maps LSP completion responses to ricecoder models
13#[derive(Debug, Clone)]
14pub struct CompletionMapper {
15    transformer: OutputTransformer,
16}
17
18impl CompletionMapper {
19    /// Create a new completion mapper
20    pub fn new() -> Self {
21        Self {
22            transformer: OutputTransformer::new(),
23        }
24    }
25
26    /// Create a mapper with custom transformations
27    pub fn with_transformer(transformer: OutputTransformer) -> Self {
28        Self { transformer }
29    }
30
31    /// Map an LSP completion response to ricecoder models
32    ///
33    /// # Arguments
34    ///
35    /// * `response` - The LSP server response (typically from textDocument/completion)
36    /// * `rules` - The mapping rules from configuration
37    ///
38    /// # Returns
39    ///
40    /// A vector of mapped completion items
41    pub fn map(&self, response: &Value, rules: &CompletionMappingRules) -> Result<Vec<Value>> {
42        self.transformer.transform_completion(response, rules)
43    }
44
45    /// Map a single completion item
46    ///
47    /// This is useful for mapping individual items when the response structure
48    /// doesn't match the expected array format.
49    pub fn map_item(&self, item: &Value, rules: &CompletionMappingRules) -> Result<Value> {
50        // Create a wrapper response with the item
51        let wrapped = serde_json::json!({
52            "result": {
53                "items": [item]
54            }
55        });
56
57        // Use default rules that expect this structure
58        let default_rules = CompletionMappingRules {
59            items_path: "$.result.items".to_string(),
60            field_mappings: rules.field_mappings.clone(),
61            transform: rules.transform.clone(),
62        };
63
64        let results = self.transformer.transform_completion(&wrapped, &default_rules)?;
65
66        if results.is_empty() {
67            return Err(crate::error::ExternalLspError::TransformationError(
68                "Failed to map completion item".to_string(),
69            ));
70        }
71
72        Ok(results[0].clone())
73    }
74}
75
76impl Default for CompletionMapper {
77    fn default() -> Self {
78        Self::new()
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85    use std::collections::HashMap;
86
87    #[test]
88    fn test_map_completion_response() {
89        let mapper = CompletionMapper::new();
90        let response = serde_json::json!({
91            "result": {
92                "items": [
93                    {
94                        "label": "foo",
95                        "kind": 12,
96                        "detail": "function",
97                        "documentation": "A foo function"
98                    },
99                    {
100                        "label": "bar",
101                        "kind": 13,
102                        "detail": "variable",
103                        "documentation": "A bar variable"
104                    }
105                ]
106            }
107        });
108
109        let mut field_mappings = HashMap::new();
110        field_mappings.insert("label".to_string(), "$.label".to_string());
111        field_mappings.insert("kind".to_string(), "$.kind".to_string());
112        field_mappings.insert("detail".to_string(), "$.detail".to_string());
113
114        let rules = CompletionMappingRules {
115            items_path: "$.result.items".to_string(),
116            field_mappings,
117            transform: None,
118        };
119
120        let results = mapper.map(&response, &rules).unwrap();
121        assert_eq!(results.len(), 2);
122        assert_eq!(results[0]["label"], "foo");
123        assert_eq!(results[1]["label"], "bar");
124    }
125
126    #[test]
127    fn test_map_completion_with_custom_structure() {
128        let mapper = CompletionMapper::new();
129        let response = serde_json::json!({
130            "completions": [
131                {"name": "foo", "type": "function"},
132                {"name": "bar", "type": "variable"}
133            ]
134        });
135
136        let mut field_mappings = HashMap::new();
137        field_mappings.insert("label".to_string(), "$.name".to_string());
138        field_mappings.insert("kind".to_string(), "$.type".to_string());
139
140        let rules = CompletionMappingRules {
141            items_path: "$.completions".to_string(),
142            field_mappings,
143            transform: None,
144        };
145
146        let results = mapper.map(&response, &rules).unwrap();
147        assert_eq!(results.len(), 2);
148        assert_eq!(results[0]["label"], "foo");
149        assert_eq!(results[0]["kind"], "function");
150    }
151
152    #[test]
153    fn test_map_single_item() {
154        let mapper = CompletionMapper::new();
155        let item = serde_json::json!({
156            "label": "test",
157            "kind": 12,
158            "detail": "test function"
159        });
160
161        let mut field_mappings = HashMap::new();
162        field_mappings.insert("label".to_string(), "$.label".to_string());
163        field_mappings.insert("kind".to_string(), "$.kind".to_string());
164
165        let rules = CompletionMappingRules {
166            items_path: "$.result.items".to_string(),
167            field_mappings,
168            transform: None,
169        };
170
171        let result = mapper.map_item(&item, &rules).unwrap();
172        assert_eq!(result["label"], "test");
173        assert_eq!(result["kind"], 12);
174    }
175
176    #[test]
177    fn test_map_empty_response() {
178        let mapper = CompletionMapper::new();
179        let response = serde_json::json!({
180            "result": {
181                "items": []
182            }
183        });
184
185        let field_mappings = HashMap::new();
186        let rules = CompletionMappingRules {
187            items_path: "$.result.items".to_string(),
188            field_mappings,
189            transform: None,
190        };
191
192        let results = mapper.map(&response, &rules).unwrap();
193        assert_eq!(results.len(), 0);
194    }
195}