1use crate::{
29 ElicitClient, ElicitError, ElicitErrorKind, ElicitResult, Elicitation, Generator, Prompt,
30 Select, mcp,
31};
32use std::io;
33
34#[derive(Debug, Clone, PartialEq, Eq, Hash)]
42pub enum IoErrorGenerationMode {
43 NotFound(String),
45 PermissionDenied(String),
47 ConnectionRefused(String),
49 ConnectionReset(String),
51 BrokenPipe(String),
53 AlreadyExists(String),
55 InvalidInput(String),
57 TimedOut(String),
59 UnexpectedEof(String),
61 Other(String),
63}
64
65impl IoErrorGenerationMode {
66 pub fn error_kind(&self) -> io::ErrorKind {
68 match self {
69 IoErrorGenerationMode::NotFound(_) => io::ErrorKind::NotFound,
70 IoErrorGenerationMode::PermissionDenied(_) => io::ErrorKind::PermissionDenied,
71 IoErrorGenerationMode::ConnectionRefused(_) => io::ErrorKind::ConnectionRefused,
72 IoErrorGenerationMode::ConnectionReset(_) => io::ErrorKind::ConnectionReset,
73 IoErrorGenerationMode::BrokenPipe(_) => io::ErrorKind::BrokenPipe,
74 IoErrorGenerationMode::AlreadyExists(_) => io::ErrorKind::AlreadyExists,
75 IoErrorGenerationMode::InvalidInput(_) => io::ErrorKind::InvalidInput,
76 IoErrorGenerationMode::TimedOut(_) => io::ErrorKind::TimedOut,
77 IoErrorGenerationMode::UnexpectedEof(_) => io::ErrorKind::UnexpectedEof,
78 IoErrorGenerationMode::Other(_) => io::ErrorKind::Other,
79 }
80 }
81
82 pub fn message(&self) -> &str {
84 match self {
85 IoErrorGenerationMode::NotFound(msg)
86 | IoErrorGenerationMode::PermissionDenied(msg)
87 | IoErrorGenerationMode::ConnectionRefused(msg)
88 | IoErrorGenerationMode::ConnectionReset(msg)
89 | IoErrorGenerationMode::BrokenPipe(msg)
90 | IoErrorGenerationMode::AlreadyExists(msg)
91 | IoErrorGenerationMode::InvalidInput(msg)
92 | IoErrorGenerationMode::TimedOut(msg)
93 | IoErrorGenerationMode::UnexpectedEof(msg)
94 | IoErrorGenerationMode::Other(msg) => msg,
95 }
96 }
97}
98
99impl Select for IoErrorGenerationMode {
100 fn options() -> &'static [Self] {
101 &[]
104 }
105
106 fn labels() -> &'static [&'static str] {
107 &[
108 "NotFound",
109 "PermissionDenied",
110 "ConnectionRefused",
111 "ConnectionReset",
112 "BrokenPipe",
113 "AlreadyExists",
114 "InvalidInput",
115 "TimedOut",
116 "UnexpectedEof",
117 "Other",
118 ]
119 }
120
121 fn from_label(label: &str) -> Option<Self> {
122 match label {
124 "NotFound" => Some(IoErrorGenerationMode::NotFound(String::new())),
125 "PermissionDenied" => Some(IoErrorGenerationMode::PermissionDenied(String::new())),
126 "ConnectionRefused" => Some(IoErrorGenerationMode::ConnectionRefused(String::new())),
127 "ConnectionReset" => Some(IoErrorGenerationMode::ConnectionReset(String::new())),
128 "BrokenPipe" => Some(IoErrorGenerationMode::BrokenPipe(String::new())),
129 "AlreadyExists" => Some(IoErrorGenerationMode::AlreadyExists(String::new())),
130 "InvalidInput" => Some(IoErrorGenerationMode::InvalidInput(String::new())),
131 "TimedOut" => Some(IoErrorGenerationMode::TimedOut(String::new())),
132 "UnexpectedEof" => Some(IoErrorGenerationMode::UnexpectedEof(String::new())),
133 "Other" => Some(IoErrorGenerationMode::Other(String::new())),
134 _ => None,
135 }
136 }
137}
138
139crate::default_style!(IoErrorGenerationMode => IoErrorGenerationModeStyle);
140
141impl Prompt for IoErrorGenerationMode {
142 fn prompt() -> Option<&'static str> {
143 Some("Select the type of IO error to create for testing:")
144 }
145}
146
147impl Elicitation for IoErrorGenerationMode {
148 type Style = IoErrorGenerationModeStyle;
149
150 async fn elicit(client: &ElicitClient<'_>) -> ElicitResult<Self> {
151 let params = mcp::select_params(
152 Self::prompt().unwrap_or("Select an option:"),
153 Self::labels(),
154 );
155
156 let result = client
157 .peer()
158 .call_tool(rmcp::model::CallToolRequestParams {
159 meta: None,
160 name: mcp::tool_names::elicit_select().into(),
161 arguments: Some(params),
162 task: None,
163 })
164 .await?;
165
166 let value = mcp::extract_value(result)?;
167 let label = mcp::parse_string(value)?;
168
169 let selected = Self::from_label(&label).ok_or_else(|| {
170 ElicitError::new(ElicitErrorKind::ParseError(
171 "Invalid IO error kind".to_string(),
172 ))
173 })?;
174
175 let message = String::elicit(client).await?;
177
178 let mode = match selected {
180 IoErrorGenerationMode::NotFound(_) => IoErrorGenerationMode::NotFound(message),
181 IoErrorGenerationMode::PermissionDenied(_) => {
182 IoErrorGenerationMode::PermissionDenied(message)
183 }
184 IoErrorGenerationMode::ConnectionRefused(_) => {
185 IoErrorGenerationMode::ConnectionRefused(message)
186 }
187 IoErrorGenerationMode::ConnectionReset(_) => {
188 IoErrorGenerationMode::ConnectionReset(message)
189 }
190 IoErrorGenerationMode::BrokenPipe(_) => IoErrorGenerationMode::BrokenPipe(message),
191 IoErrorGenerationMode::AlreadyExists(_) => {
192 IoErrorGenerationMode::AlreadyExists(message)
193 }
194 IoErrorGenerationMode::InvalidInput(_) => IoErrorGenerationMode::InvalidInput(message),
195 IoErrorGenerationMode::TimedOut(_) => IoErrorGenerationMode::TimedOut(message),
196 IoErrorGenerationMode::UnexpectedEof(_) => {
197 IoErrorGenerationMode::UnexpectedEof(message)
198 }
199 IoErrorGenerationMode::Other(_) => IoErrorGenerationMode::Other(message),
200 };
201
202 Ok(mode)
203 }
204}
205
206#[derive(Debug, Clone)]
210pub struct IoErrorGenerator {
211 mode: IoErrorGenerationMode,
212}
213
214impl IoErrorGenerator {
215 pub fn new(mode: IoErrorGenerationMode) -> Self {
217 Self { mode }
218 }
219
220 pub fn mode(&self) -> &IoErrorGenerationMode {
222 &self.mode
223 }
224}
225
226impl Generator for IoErrorGenerator {
227 type Target = io::Error;
228
229 fn generate(&self) -> Self::Target {
230 io::Error::new(self.mode.error_kind(), self.mode.message())
231 }
232}
233
234crate::default_style!(io::Error => IoErrorStyle);
236
237impl Prompt for io::Error {
238 fn prompt() -> Option<&'static str> {
239 Some("Create an IO error for testing:")
240 }
241}
242
243impl Elicitation for io::Error {
244 type Style = IoErrorStyle;
245
246 async fn elicit(client: &ElicitClient<'_>) -> ElicitResult<Self> {
247 tracing::debug!("Eliciting io::Error for testing");
248
249 let mode = IoErrorGenerationMode::elicit(client).await?;
251
252 let generator = IoErrorGenerator::new(mode);
254 Ok(generator.generate())
255 }
256}
257
258#[cfg(feature = "serde_json")]
263mod json_error {
264 use super::*;
265
266 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
270 pub enum JsonErrorGenerationMode {
271 SyntaxError,
273 EofWhileParsing,
275 InvalidNumber,
277 InvalidEscape,
279 InvalidUnicode,
281 }
282
283 impl Select for JsonErrorGenerationMode {
284 fn options() -> &'static [Self] {
285 &[
286 JsonErrorGenerationMode::SyntaxError,
287 JsonErrorGenerationMode::EofWhileParsing,
288 JsonErrorGenerationMode::InvalidNumber,
289 JsonErrorGenerationMode::InvalidEscape,
290 JsonErrorGenerationMode::InvalidUnicode,
291 ]
292 }
293
294 fn labels() -> &'static [&'static str] {
295 &[
296 "Syntax Error",
297 "EOF While Parsing",
298 "Invalid Number",
299 "Invalid Escape",
300 "Invalid Unicode",
301 ]
302 }
303
304 fn from_label(label: &str) -> Option<Self> {
305 match label {
306 "Syntax Error" => Some(JsonErrorGenerationMode::SyntaxError),
307 "EOF While Parsing" => Some(JsonErrorGenerationMode::EofWhileParsing),
308 "Invalid Number" => Some(JsonErrorGenerationMode::InvalidNumber),
309 "Invalid Escape" => Some(JsonErrorGenerationMode::InvalidEscape),
310 "Invalid Unicode" => Some(JsonErrorGenerationMode::InvalidUnicode),
311 _ => None,
312 }
313 }
314 }
315
316 crate::default_style!(JsonErrorGenerationMode => JsonErrorGenerationModeStyle);
317
318 impl Prompt for JsonErrorGenerationMode {
319 fn prompt() -> Option<&'static str> {
320 Some("Select the type of JSON error to create for testing:")
321 }
322 }
323
324 impl Elicitation for JsonErrorGenerationMode {
325 type Style = JsonErrorGenerationModeStyle;
326
327 async fn elicit(client: &ElicitClient<'_>) -> ElicitResult<Self> {
328 let params = mcp::select_params(
329 Self::prompt().unwrap_or("Select an option:"),
330 Self::labels(),
331 );
332
333 let result = client
334 .peer()
335 .call_tool(rmcp::model::CallToolRequestParams {
336 meta: None,
337 name: mcp::tool_names::elicit_select().into(),
338 arguments: Some(params),
339 task: None,
340 })
341 .await?;
342
343 let value = mcp::extract_value(result)?;
344 let label = mcp::parse_string(value)?;
345
346 Self::from_label(&label).ok_or_else(|| {
347 ElicitError::new(ElicitErrorKind::ParseError(
348 "Invalid JSON error kind".to_string(),
349 ))
350 })
351 }
352 }
353
354 #[derive(Debug, Clone, Copy)]
358 pub struct JsonErrorGenerator {
359 mode: JsonErrorGenerationMode,
360 }
361
362 impl JsonErrorGenerator {
363 pub fn new(mode: JsonErrorGenerationMode) -> Self {
365 Self { mode }
366 }
367
368 pub fn mode(&self) -> JsonErrorGenerationMode {
370 self.mode
371 }
372 }
373
374 impl Generator for JsonErrorGenerator {
375 type Target = serde_json::Error;
376
377 fn generate(&self) -> Self::Target {
378 let invalid_json = match self.mode {
380 JsonErrorGenerationMode::SyntaxError => "{invalid}",
381 JsonErrorGenerationMode::EofWhileParsing => "{\"key\":",
382 JsonErrorGenerationMode::InvalidNumber => "{\"num\": 1e999999}",
383 JsonErrorGenerationMode::InvalidEscape => r#"{"str": "\x"}"#,
384 JsonErrorGenerationMode::InvalidUnicode => r#"{"str": "\uDEAD"}"#,
385 };
386
387 serde_json::from_str::<serde_json::Value>(invalid_json)
389 .expect_err("Invalid JSON should always error")
390 }
391 }
392
393 crate::default_style!(serde_json::Error => JsonErrorStyle);
395
396 impl Prompt for serde_json::Error {
397 fn prompt() -> Option<&'static str> {
398 Some("Create a JSON parsing error for testing:")
399 }
400 }
401
402 impl Elicitation for serde_json::Error {
403 type Style = JsonErrorStyle;
404
405 async fn elicit(client: &ElicitClient<'_>) -> ElicitResult<Self> {
406 tracing::debug!("Eliciting serde_json::Error for testing");
407
408 let mode = JsonErrorGenerationMode::elicit(client).await?;
410
411 let generator = JsonErrorGenerator::new(mode);
413 Ok(generator.generate())
414 }
415 }
416}
417
418#[cfg(feature = "serde_json")]
419pub use json_error::{JsonErrorGenerationMode, JsonErrorGenerator};
420
421#[cfg(test)]
422mod tests {
423 use super::*;
424
425 #[test]
426 fn test_io_error_generation() {
427 let mode = IoErrorGenerationMode::NotFound("config.toml".to_string());
428 let generator = IoErrorGenerator::new(mode);
429 let error = generator.generate();
430
431 assert_eq!(error.kind(), io::ErrorKind::NotFound);
432 assert!(error.to_string().contains("config.toml"));
433 }
434
435 #[test]
436 fn test_io_error_kinds() {
437 let modes = vec![
438 IoErrorGenerationMode::PermissionDenied("test".to_string()),
439 IoErrorGenerationMode::ConnectionRefused("test".to_string()),
440 IoErrorGenerationMode::BrokenPipe("test".to_string()),
441 ];
442
443 for mode in modes {
444 let generator = IoErrorGenerator::new(mode.clone());
445 let error = generator.generate();
446 assert_eq!(error.kind(), mode.error_kind());
447 }
448 }
449
450 #[cfg(feature = "serde_json")]
451 #[test]
452 fn test_json_error_generation() {
453 let mode = JsonErrorGenerationMode::SyntaxError;
454 let generator = JsonErrorGenerator::new(mode);
455 let error = generator.generate();
456
457 assert!(!error.to_string().is_empty());
459 }
460
461 #[cfg(feature = "serde_json")]
462 #[test]
463 fn test_json_error_kinds() {
464 let modes = vec![
465 JsonErrorGenerationMode::SyntaxError,
466 JsonErrorGenerationMode::EofWhileParsing,
467 JsonErrorGenerationMode::InvalidNumber,
468 ];
469
470 for mode in modes {
471 let generator = JsonErrorGenerator::new(mode);
472 let error = generator.generate();
473 assert!(!error.to_string().is_empty());
475 }
476 }
477}