turul_mcp_protocol_2025_06_18/
completion.rs1use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use std::collections::HashMap;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11#[serde(rename_all = "camelCase")]
12pub struct ResourceTemplateReference {
13 #[serde(rename = "type")]
14 pub ref_type: String, #[serde(rename = "uri")]
17 pub uri: String,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
22#[serde(rename_all = "camelCase")]
23pub struct PromptReference {
24 #[serde(rename = "type")]
25 pub ref_type: String, pub name: String,
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub description: Option<String>,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35#[serde(untagged)]
36pub enum CompletionReference {
37 ResourceTemplate(ResourceTemplateReference),
38 Prompt(PromptReference),
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43#[serde(rename_all = "camelCase")]
44pub struct CompletionContext {
45 #[serde(skip_serializing_if = "Option::is_none")]
47 pub arguments: Option<HashMap<String, String>>,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
52#[serde(rename_all = "camelCase")]
53pub struct CompleteArgument {
54 pub name: String,
56 pub value: String,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
62#[serde(rename_all = "camelCase")]
63pub struct CompleteParams {
64 #[serde(rename = "ref")]
66 pub reference: CompletionReference,
67 pub argument: CompleteArgument,
69 #[serde(skip_serializing_if = "Option::is_none")]
71 pub context: Option<CompletionContext>,
72 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
74 pub meta: Option<HashMap<String, Value>>,
75}
76
77impl CompleteParams {
78 pub fn new(reference: CompletionReference, argument: CompleteArgument) -> Self {
79 Self {
80 reference,
81 argument,
82 context: None,
83 meta: None,
84 }
85 }
86
87 pub fn with_context(mut self, context: CompletionContext) -> Self {
88 self.context = Some(context);
89 self
90 }
91
92 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
93 self.meta = Some(meta);
94 self
95 }
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
100#[serde(rename_all = "camelCase")]
101pub struct CompleteRequest {
102 pub method: String,
104 pub params: CompleteParams,
106}
107
108impl CompleteRequest {
109 pub fn new(reference: CompletionReference, argument: CompleteArgument) -> Self {
110 Self {
111 method: "completion/complete".to_string(),
112 params: CompleteParams::new(reference, argument),
113 }
114 }
115
116 pub fn with_context(mut self, context: CompletionContext) -> Self {
117 self.params = self.params.with_context(context);
118 self
119 }
120
121 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
122 self.params = self.params.with_meta(meta);
123 self
124 }
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
129#[serde(rename_all = "camelCase")]
130pub struct CompletionResult {
131 pub values: Vec<String>,
133 #[serde(skip_serializing_if = "Option::is_none")]
135 pub total: Option<u32>,
136 #[serde(skip_serializing_if = "Option::is_none")]
138 pub has_more: Option<bool>,
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize)]
143#[serde(rename_all = "camelCase")]
144pub struct CompleteResult {
145 pub completion: CompletionResult,
147 #[serde(
149 default,
150 skip_serializing_if = "Option::is_none",
151 alias = "_meta",
152 rename = "_meta"
153 )]
154 pub meta: Option<HashMap<String, Value>>,
155}
156
157impl CompletionResult {
158 pub fn new(values: Vec<String>) -> Self {
159 Self {
160 values,
161 total: None,
162 has_more: None,
163 }
164 }
165
166 pub fn with_total(mut self, total: u32) -> Self {
167 self.total = Some(total);
168 self
169 }
170
171 pub fn with_has_more(mut self, has_more: bool) -> Self {
172 self.has_more = Some(has_more);
173 self
174 }
175}
176
177impl CompleteResult {
178 pub fn new(completion: CompletionResult) -> Self {
179 Self {
180 completion,
181 meta: None,
182 }
183 }
184
185 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
186 self.meta = Some(meta);
187 self
188 }
189}
190
191impl ResourceTemplateReference {
193 pub fn new(uri: impl Into<String>) -> Self {
194 Self {
195 ref_type: "ref/resource".to_string(),
196 uri: uri.into(),
197 }
198 }
199}
200
201impl PromptReference {
202 pub fn new(name: impl Into<String>) -> Self {
203 Self {
204 ref_type: "ref/prompt".to_string(),
205 name: name.into(),
206 description: None,
207 }
208 }
209
210 pub fn with_description(mut self, description: impl Into<String>) -> Self {
211 self.description = Some(description.into());
212 self
213 }
214}
215
216impl CompletionReference {
217 pub fn resource(uri: impl Into<String>) -> Self {
218 Self::ResourceTemplate(ResourceTemplateReference::new(uri))
219 }
220
221 pub fn prompt(name: impl Into<String>) -> Self {
222 Self::Prompt(PromptReference::new(name))
223 }
224}
225
226impl CompleteArgument {
227 pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
228 Self {
229 name: name.into(),
230 value: value.into(),
231 }
232 }
233}
234
235impl Default for CompletionContext {
236 fn default() -> Self {
237 Self::new()
238 }
239}
240
241impl CompletionContext {
242 pub fn new() -> Self {
243 Self { arguments: None }
244 }
245
246 pub fn with_arguments(mut self, arguments: HashMap<String, String>) -> Self {
247 self.arguments = Some(arguments);
248 self
249 }
250}
251
252use crate::traits::*;
254
255impl Params for CompleteParams {}
256
257impl HasMetaParam for CompleteParams {
258 fn meta(&self) -> Option<&HashMap<String, Value>> {
259 self.meta.as_ref()
260 }
261}
262
263impl HasMethod for CompleteRequest {
264 fn method(&self) -> &str {
265 &self.method
266 }
267}
268
269impl HasParams for CompleteRequest {
270 fn params(&self) -> Option<&dyn Params> {
271 Some(&self.params)
272 }
273}
274
275impl HasData for CompleteResult {
276 fn data(&self) -> HashMap<String, Value> {
277 let mut data = HashMap::new();
278 data.insert(
279 "completion".to_string(),
280 serde_json::to_value(&self.completion).unwrap_or(Value::Null),
281 );
282 data
283 }
284}
285
286impl HasMeta for CompleteResult {
287 fn meta(&self) -> Option<HashMap<String, Value>> {
288 self.meta.clone()
289 }
290}
291
292impl RpcResult for CompleteResult {}
293
294#[cfg(test)]
301mod tests {
302 use super::*;
303 use serde_json::json;
304
305 #[test]
306 fn test_resource_template_reference() {
307 let ref_obj = ResourceTemplateReference::new("file:///test/{name}.txt");
308
309 assert_eq!(ref_obj.ref_type, "ref/resource");
310 assert_eq!(ref_obj.uri, "file:///test/{name}.txt");
311
312 let json_value = serde_json::to_value(&ref_obj).unwrap();
313 assert_eq!(json_value["type"], "ref/resource");
314 assert_eq!(json_value["uri"], "file:///test/{name}.txt");
315 }
316
317 #[test]
318 fn test_prompt_reference() {
319 let ref_obj = PromptReference::new("test_prompt").with_description("A test prompt");
320
321 assert_eq!(ref_obj.ref_type, "ref/prompt");
322 assert_eq!(ref_obj.name, "test_prompt");
323 assert_eq!(ref_obj.description, Some("A test prompt".to_string()));
324
325 let json_value = serde_json::to_value(&ref_obj).unwrap();
326 assert_eq!(json_value["type"], "ref/prompt");
327 assert_eq!(json_value["name"], "test_prompt");
328 assert_eq!(json_value["description"], "A test prompt");
329 }
330
331 #[test]
332 fn test_completion_reference_union() {
333 let resource_ref = CompletionReference::resource("file:///test.txt");
334 let prompt_ref = CompletionReference::prompt("my_prompt");
335
336 let resource_json = serde_json::to_value(&resource_ref).unwrap();
338 let prompt_json = serde_json::to_value(&prompt_ref).unwrap();
339
340 assert_eq!(resource_json["type"], "ref/resource");
341 assert_eq!(resource_json["uri"], "file:///test.txt");
342
343 assert_eq!(prompt_json["type"], "ref/prompt");
344 assert_eq!(prompt_json["name"], "my_prompt");
345 }
346
347 #[test]
348 fn test_complete_request_matches_typescript_spec() {
349 let mut meta = HashMap::new();
351 meta.insert("requestId".to_string(), json!("req-123"));
352
353 let mut context_args = HashMap::new();
354 context_args.insert("userId".to_string(), "123".to_string());
355
356 let context = CompletionContext::new().with_arguments(context_args);
357
358 let request = CompleteRequest::new(
359 CompletionReference::prompt("test_prompt"),
360 CompleteArgument::new("arg_name", "partial_value"),
361 )
362 .with_context(context)
363 .with_meta(meta);
364
365 let json_value = serde_json::to_value(&request).unwrap();
366
367 assert_eq!(json_value["method"], "completion/complete");
368 assert!(json_value["params"].is_object());
369 assert!(json_value["params"]["ref"].is_object());
370 assert_eq!(json_value["params"]["ref"]["type"], "ref/prompt");
371 assert_eq!(json_value["params"]["ref"]["name"], "test_prompt");
372 assert_eq!(json_value["params"]["argument"]["name"], "arg_name");
373 assert_eq!(json_value["params"]["argument"]["value"], "partial_value");
374 assert!(json_value["params"]["context"].is_object());
375 assert_eq!(
376 json_value["params"]["context"]["arguments"]["userId"],
377 "123"
378 );
379 assert_eq!(json_value["params"]["_meta"]["requestId"], "req-123");
380 }
381
382 #[test]
383 fn test_complete_result_matches_typescript_spec() {
384 let mut meta = HashMap::new();
386 meta.insert("executionTime".to_string(), json!(42));
387
388 let completion = CompletionResult::new(vec![
389 "option1".to_string(),
390 "option2".to_string(),
391 "option3".to_string(),
392 ])
393 .with_total(100)
394 .with_has_more(true);
395
396 let result = CompleteResult::new(completion).with_meta(meta);
397
398 let json_value = serde_json::to_value(&result).unwrap();
399
400 assert!(json_value["completion"].is_object());
401 assert!(json_value["completion"]["values"].is_array());
402 assert_eq!(
403 json_value["completion"]["values"].as_array().unwrap().len(),
404 3
405 );
406 assert_eq!(json_value["completion"]["values"][0], "option1");
407 assert_eq!(json_value["completion"]["total"], 100);
408 assert_eq!(json_value["completion"]["hasMore"], true);
409 assert_eq!(json_value["_meta"]["executionTime"], 42);
410 }
411
412 #[test]
413 fn test_serialization() {
414 let request = CompleteRequest::new(
415 CompletionReference::resource("file:///test/{id}.txt"),
416 CompleteArgument::new("id", "test"),
417 );
418
419 let json = serde_json::to_string(&request).unwrap();
420 assert!(json.contains("completion/complete"));
421 assert!(json.contains("ref/resource"));
422 assert!(json.contains("file:///test/{id}.txt"));
423
424 let parsed: CompleteRequest = serde_json::from_str(&json).unwrap();
425 assert_eq!(parsed.method, "completion/complete");
426 }
427}