1use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::collections::HashMap;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
94#[serde(rename_all = "camelCase")]
95pub struct ElicitInputRequest {
96 pub elicitation_id: String,
98
99 pub input_type: InputType,
101
102 pub prompt: String,
104
105 #[serde(skip_serializing_if = "Option::is_none")]
107 pub description: Option<String>,
108
109 #[serde(skip_serializing_if = "Option::is_none")]
111 pub default: Option<Value>,
112
113 #[serde(skip_serializing_if = "Option::is_none")]
115 pub validation: Option<InputValidation>,
116
117 #[serde(flatten)]
119 pub metadata: HashMap<String, Value>,
120}
121
122#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
124#[serde(rename_all = "camelCase")]
125pub enum InputType {
126 Text,
128 Textarea,
130 Boolean,
132 Number,
134 Select,
136 MultiSelect,
138 FilePath,
140 DirectoryPath,
142 Password,
144 Date,
146 Time,
148 DateTime,
150 Color,
152 Url,
154 Email,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
160#[serde(rename_all = "camelCase")]
161pub struct InputValidation {
162 #[serde(default)]
164 pub required: bool,
165
166 #[serde(skip_serializing_if = "Option::is_none")]
168 pub min: Option<f64>,
169
170 #[serde(skip_serializing_if = "Option::is_none")]
172 pub max: Option<f64>,
173
174 #[serde(skip_serializing_if = "Option::is_none")]
176 pub pattern: Option<String>,
177
178 #[serde(skip_serializing_if = "Option::is_none")]
180 pub options: Option<Vec<SelectOption>>,
181
182 #[serde(skip_serializing_if = "Option::is_none")]
184 pub message: Option<String>,
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize)]
189#[serde(rename_all = "camelCase")]
190pub struct SelectOption {
191 pub value: Value,
193
194 pub label: String,
196
197 #[serde(skip_serializing_if = "Option::is_none")]
199 pub description: Option<String>,
200
201 #[serde(default)]
203 pub disabled: bool,
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize)]
208#[serde(rename_all = "camelCase")]
209pub struct ElicitInputResponse {
210 pub elicitation_id: String,
212
213 #[serde(skip_serializing_if = "Option::is_none")]
215 pub value: Option<Value>,
216
217 #[serde(default)]
219 pub cancelled: bool,
220
221 #[serde(skip_serializing_if = "Option::is_none")]
223 pub error: Option<String>,
224}
225
226#[derive(Debug)]
228pub struct ElicitInputBuilder {
229 elicitation_id: String,
230 input_type: InputType,
231 prompt: String,
232 description: Option<String>,
233 default: Option<Value>,
234 validation: Option<InputValidation>,
235 metadata: HashMap<String, Value>,
236}
237
238impl ElicitInputBuilder {
239 pub fn new(input_type: InputType, prompt: impl Into<String>) -> Self {
241 Self {
242 elicitation_id: uuid::Uuid::new_v4().to_string(),
243 input_type,
244 prompt: prompt.into(),
245 description: None,
246 default: None,
247 validation: None,
248 metadata: HashMap::new(),
249 }
250 }
251
252 pub fn id(mut self, id: impl Into<String>) -> Self {
254 self.elicitation_id = id.into();
255 self
256 }
257
258 pub fn description(mut self, desc: impl Into<String>) -> Self {
260 self.description = Some(desc.into());
261 self
262 }
263
264 pub fn default(mut self, value: impl Into<Value>) -> Self {
266 self.default = Some(value.into());
267 self
268 }
269
270 pub fn required(mut self) -> Self {
272 if self.validation.is_none() {
273 self.validation = Some(InputValidation {
274 required: true,
275 min: None,
276 max: None,
277 pattern: None,
278 options: None,
279 message: None,
280 });
281 } else if let Some(validation) = &mut self.validation {
282 validation.required = true;
283 }
284 self
285 }
286
287 pub fn min(mut self, min: f64) -> Self {
289 if self.validation.is_none() {
290 self.validation = Some(InputValidation {
291 required: false,
292 min: Some(min),
293 max: None,
294 pattern: None,
295 options: None,
296 message: None,
297 });
298 } else if let Some(validation) = &mut self.validation {
299 validation.min = Some(min);
300 }
301 self
302 }
303
304 pub fn max(mut self, max: f64) -> Self {
306 if self.validation.is_none() {
307 self.validation = Some(InputValidation {
308 required: false,
309 min: None,
310 max: Some(max),
311 pattern: None,
312 options: None,
313 message: None,
314 });
315 } else if let Some(validation) = &mut self.validation {
316 validation.max = Some(max);
317 }
318 self
319 }
320
321 pub fn pattern(mut self, pattern: impl Into<String>) -> Self {
323 if self.validation.is_none() {
324 self.validation = Some(InputValidation {
325 required: false,
326 min: None,
327 max: None,
328 pattern: Some(pattern.into()),
329 options: None,
330 message: None,
331 });
332 } else if let Some(validation) = &mut self.validation {
333 validation.pattern = Some(pattern.into());
334 }
335 self
336 }
337
338 pub fn options(mut self, options: Vec<SelectOption>) -> Self {
340 if self.validation.is_none() {
341 self.validation = Some(InputValidation {
342 required: false,
343 min: None,
344 max: None,
345 pattern: None,
346 options: Some(options),
347 message: None,
348 });
349 } else if let Some(validation) = &mut self.validation {
350 validation.options = Some(options);
351 }
352 self
353 }
354
355 pub fn metadata(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
357 self.metadata.insert(key.into(), value.into());
358 self
359 }
360
361 pub fn build(self) -> ElicitInputRequest {
363 ElicitInputRequest {
364 elicitation_id: self.elicitation_id,
365 input_type: self.input_type,
366 prompt: self.prompt,
367 description: self.description,
368 default: self.default,
369 validation: self.validation,
370 metadata: self.metadata,
371 }
372 }
373}
374
375pub fn elicit_text(prompt: impl Into<String>) -> ElicitInputBuilder {
377 ElicitInputBuilder::new(InputType::Text, prompt)
378}
379
380pub fn elicit_boolean(prompt: impl Into<String>) -> ElicitInputBuilder {
382 ElicitInputBuilder::new(InputType::Boolean, prompt)
383}
384
385pub fn elicit_select(prompt: impl Into<String>, options: Vec<SelectOption>) -> ElicitInputBuilder {
387 ElicitInputBuilder::new(InputType::Select, prompt).options(options)
388}
389
390pub fn elicit_number(prompt: impl Into<String>) -> ElicitInputBuilder {
392 ElicitInputBuilder::new(InputType::Number, prompt)
393}
394
395pub fn elicit_file(prompt: impl Into<String>) -> ElicitInputBuilder {
397 ElicitInputBuilder::new(InputType::FilePath, prompt)
398}
399
400#[cfg(test)]
401mod tests {
402 use super::*;
403 use serde_json::json;
404
405 #[test]
406 fn test_elicit_text() {
407 let request = elicit_text("Enter your name")
408 .description("Please provide your full name")
409 .required()
410 .min(2.0)
411 .max(100.0)
412 .build();
413
414 assert_eq!(request.input_type, InputType::Text);
415 assert_eq!(request.prompt, "Enter your name");
416 assert_eq!(
417 request.description,
418 Some("Please provide your full name".to_string())
419 );
420 assert!(request.validation.as_ref().unwrap().required);
421 assert_eq!(request.validation.as_ref().unwrap().min, Some(2.0));
422 assert_eq!(request.validation.as_ref().unwrap().max, Some(100.0));
423 }
424
425 #[test]
426 fn test_elicit_select() {
427 let options = vec![
428 SelectOption {
429 value: json!("small"),
430 label: "Small".to_string(),
431 description: Some("Suitable for personal use".to_string()),
432 disabled: false,
433 },
434 SelectOption {
435 value: json!("medium"),
436 label: "Medium".to_string(),
437 description: Some("Good for small teams".to_string()),
438 disabled: false,
439 },
440 SelectOption {
441 value: json!("large"),
442 label: "Large".to_string(),
443 description: Some("For enterprise use".to_string()),
444 disabled: false,
445 },
446 ];
447
448 let request = elicit_select("Choose a size", options.clone())
449 .default(json!("medium"))
450 .build();
451
452 assert_eq!(request.input_type, InputType::Select);
453 assert_eq!(request.prompt, "Choose a size");
454 assert_eq!(request.default, Some(json!("medium")));
455 assert_eq!(
456 request
457 .validation
458 .as_ref()
459 .unwrap()
460 .options
461 .as_ref()
462 .unwrap()
463 .len(),
464 3
465 );
466 }
467
468 #[test]
469 fn test_serialization() {
470 let request = elicit_boolean("Enable feature?")
471 .default(json!(true))
472 .description("This will enable the experimental feature")
473 .build();
474
475 let json = serde_json::to_value(&request).unwrap();
476 assert_eq!(json["inputType"], "boolean");
477 assert_eq!(json["prompt"], "Enable feature?");
478 assert_eq!(json["default"], true);
479 assert_eq!(
480 json["description"],
481 "This will enable the experimental feature"
482 );
483
484 let deserialized: ElicitInputRequest = serde_json::from_value(json).unwrap();
486 assert_eq!(deserialized.prompt, request.prompt);
487 }
488}