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
128#[derive(Debug, Clone, Serialize, Deserialize)]
130#[serde(rename_all = "camelCase")]
131pub struct CompletionResult {
132 pub values: Vec<String>,
134 #[serde(skip_serializing_if = "Option::is_none")]
136 pub total: Option<u32>,
137 #[serde(skip_serializing_if = "Option::is_none")]
139 pub has_more: Option<bool>,
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
144#[serde(rename_all = "camelCase")]
145pub struct CompleteResult {
146 pub completion: CompletionResult,
148 #[serde(
150 default,
151 skip_serializing_if = "Option::is_none",
152 alias = "_meta",
153 rename = "_meta"
154 )]
155 pub meta: Option<HashMap<String, Value>>,
156}
157
158impl CompletionResult {
159 pub fn new(values: Vec<String>) -> Self {
160 Self {
161 values,
162 total: None,
163 has_more: None,
164 }
165 }
166
167 pub fn with_total(mut self, total: u32) -> Self {
168 self.total = Some(total);
169 self
170 }
171
172 pub fn with_has_more(mut self, has_more: bool) -> Self {
173 self.has_more = Some(has_more);
174 self
175 }
176}
177
178impl CompleteResult {
179 pub fn new(completion: CompletionResult) -> Self {
180 Self {
181 completion,
182 meta: None,
183 }
184 }
185
186 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
187 self.meta = Some(meta);
188 self
189 }
190}
191
192impl ResourceTemplateReference {
194 pub fn new(uri: impl Into<String>) -> Self {
195 Self {
196 ref_type: "ref/resource".to_string(),
197 uri: uri.into(),
198 }
199 }
200}
201
202impl PromptReference {
203 pub fn new(name: impl Into<String>) -> Self {
204 Self {
205 ref_type: "ref/prompt".to_string(),
206 name: name.into(),
207 description: None,
208 }
209 }
210
211 pub fn with_description(mut self, description: impl Into<String>) -> Self {
212 self.description = Some(description.into());
213 self
214 }
215}
216
217impl CompletionReference {
218 pub fn resource(uri: impl Into<String>) -> Self {
219 Self::ResourceTemplate(ResourceTemplateReference::new(uri))
220 }
221
222 pub fn prompt(name: impl Into<String>) -> Self {
223 Self::Prompt(PromptReference::new(name))
224 }
225}
226
227impl CompleteArgument {
228 pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
229 Self {
230 name: name.into(),
231 value: value.into(),
232 }
233 }
234}
235
236impl CompletionContext {
237 pub fn new() -> Self {
238 Self {
239 arguments: None,
240 }
241 }
242
243 pub fn with_arguments(mut self, arguments: HashMap<String, String>) -> Self {
244 self.arguments = Some(arguments);
245 self
246 }
247}
248
249use crate::traits::*;
251
252impl Params for CompleteParams {}
253
254impl HasMetaParam for CompleteParams {
255 fn meta(&self) -> Option<&HashMap<String, Value>> {
256 self.meta.as_ref()
257 }
258}
259
260impl HasMethod for CompleteRequest {
261 fn method(&self) -> &str {
262 &self.method
263 }
264}
265
266impl HasParams for CompleteRequest {
267 fn params(&self) -> Option<&dyn Params> {
268 Some(&self.params)
269 }
270}
271
272impl HasData for CompleteResult {
273 fn data(&self) -> HashMap<String, Value> {
274 let mut data = HashMap::new();
275 data.insert("completion".to_string(), serde_json::to_value(&self.completion).unwrap_or(Value::Null));
276 data
277 }
278}
279
280impl HasMeta for CompleteResult {
281 fn meta(&self) -> Option<HashMap<String, Value>> {
282 self.meta.clone()
283 }
284}
285
286impl RpcResult for CompleteResult {}
287
288pub trait HasCompletionMetadata {
294 fn method(&self) -> &str;
296
297 fn reference(&self) -> &CompletionReference;
299}
300
301pub trait HasCompletionContext {
303 fn argument(&self) -> &CompleteArgument;
305
306 fn context(&self) -> Option<&CompletionContext> {
308 None
309 }
310}
311
312pub trait HasCompletionHandling {
314 fn validate_request(&self, _request: &CompleteRequest) -> Result<(), String> {
316 Ok(())
317 }
318
319 fn filter_completions(&self, values: Vec<String>, current_value: &str) -> Vec<String> {
321 values
323 .into_iter()
324 .filter(|v| v.to_lowercase().starts_with(¤t_value.to_lowercase()))
325 .collect()
326 }
327}
328
329pub trait CompletionDefinition:
331 HasCompletionMetadata +
332 HasCompletionContext +
333 HasCompletionHandling
334{
335 fn to_complete_request(&self) -> CompleteRequest {
337 let mut request = CompleteRequest::new(
338 self.reference().clone(),
339 self.argument().clone()
340 );
341 if let Some(context) = self.context() {
342 request = request.with_context(context.clone());
343 }
344 request
345 }
346}
347
348impl<T> CompletionDefinition for T
350where
351 T: HasCompletionMetadata + HasCompletionContext + HasCompletionHandling
352{}
353
354#[cfg(test)]
355mod tests {
356 use super::*;
357 use serde_json::json;
358
359 #[test]
360 fn test_resource_template_reference() {
361 let ref_obj = ResourceTemplateReference::new("file:///test/{name}.txt");
362
363 assert_eq!(ref_obj.ref_type, "ref/resource");
364 assert_eq!(ref_obj.uri, "file:///test/{name}.txt");
365
366 let json_value = serde_json::to_value(&ref_obj).unwrap();
367 assert_eq!(json_value["type"], "ref/resource");
368 assert_eq!(json_value["uri"], "file:///test/{name}.txt");
369 }
370
371 #[test]
372 fn test_prompt_reference() {
373 let ref_obj = PromptReference::new("test_prompt")
374 .with_description("A test prompt");
375
376 assert_eq!(ref_obj.ref_type, "ref/prompt");
377 assert_eq!(ref_obj.name, "test_prompt");
378 assert_eq!(ref_obj.description, Some("A test prompt".to_string()));
379
380 let json_value = serde_json::to_value(&ref_obj).unwrap();
381 assert_eq!(json_value["type"], "ref/prompt");
382 assert_eq!(json_value["name"], "test_prompt");
383 assert_eq!(json_value["description"], "A test prompt");
384 }
385
386 #[test]
387 fn test_completion_reference_union() {
388 let resource_ref = CompletionReference::resource("file:///test.txt");
389 let prompt_ref = CompletionReference::prompt("my_prompt");
390
391 let resource_json = serde_json::to_value(&resource_ref).unwrap();
393 let prompt_json = serde_json::to_value(&prompt_ref).unwrap();
394
395 assert_eq!(resource_json["type"], "ref/resource");
396 assert_eq!(resource_json["uri"], "file:///test.txt");
397
398 assert_eq!(prompt_json["type"], "ref/prompt");
399 assert_eq!(prompt_json["name"], "my_prompt");
400 }
401
402 #[test]
403 fn test_complete_request_matches_typescript_spec() {
404 let mut meta = HashMap::new();
406 meta.insert("requestId".to_string(), json!("req-123"));
407
408 let mut context_args = HashMap::new();
409 context_args.insert("userId".to_string(), "123".to_string());
410
411 let context = CompletionContext::new().with_arguments(context_args);
412
413 let request = CompleteRequest::new(
414 CompletionReference::prompt("test_prompt"),
415 CompleteArgument::new("arg_name", "partial_value")
416 )
417 .with_context(context)
418 .with_meta(meta);
419
420 let json_value = serde_json::to_value(&request).unwrap();
421
422 assert_eq!(json_value["method"], "completion/complete");
423 assert!(json_value["params"].is_object());
424 assert!(json_value["params"]["ref"].is_object());
425 assert_eq!(json_value["params"]["ref"]["type"], "ref/prompt");
426 assert_eq!(json_value["params"]["ref"]["name"], "test_prompt");
427 assert_eq!(json_value["params"]["argument"]["name"], "arg_name");
428 assert_eq!(json_value["params"]["argument"]["value"], "partial_value");
429 assert!(json_value["params"]["context"].is_object());
430 assert_eq!(json_value["params"]["context"]["arguments"]["userId"], "123");
431 assert_eq!(json_value["params"]["_meta"]["requestId"], "req-123");
432 }
433
434 #[test]
435 fn test_complete_result_matches_typescript_spec() {
436 let mut meta = HashMap::new();
438 meta.insert("executionTime".to_string(), json!(42));
439
440 let completion = CompletionResult::new(vec![
441 "option1".to_string(),
442 "option2".to_string(),
443 "option3".to_string()
444 ])
445 .with_total(100)
446 .with_has_more(true);
447
448 let result = CompleteResult::new(completion)
449 .with_meta(meta);
450
451 let json_value = serde_json::to_value(&result).unwrap();
452
453 assert!(json_value["completion"].is_object());
454 assert!(json_value["completion"]["values"].is_array());
455 assert_eq!(json_value["completion"]["values"].as_array().unwrap().len(), 3);
456 assert_eq!(json_value["completion"]["values"][0], "option1");
457 assert_eq!(json_value["completion"]["total"], 100);
458 assert_eq!(json_value["completion"]["hasMore"], true);
459 assert_eq!(json_value["_meta"]["executionTime"], 42);
460 }
461
462 #[test]
463 fn test_serialization() {
464 let request = CompleteRequest::new(
465 CompletionReference::resource("file:///test/{id}.txt"),
466 CompleteArgument::new("id", "test")
467 );
468
469 let json = serde_json::to_string(&request).unwrap();
470 assert!(json.contains("completion/complete"));
471 assert!(json.contains("ref/resource"));
472 assert!(json.contains("file:///test/{id}.txt"));
473
474 let parsed: CompleteRequest = serde_json::from_str(&json).unwrap();
475 assert_eq!(parsed.method, "completion/complete");
476 }
477}