use crate::{
ElicitCommunicator, ElicitError, ElicitErrorKind, ElicitResult, Elicitation, Generator, Prompt,
Select, mcp,
};
use std::io;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum IoErrorGenerationMode {
NotFound(String),
PermissionDenied(String),
ConnectionRefused(String),
ConnectionReset(String),
BrokenPipe(String),
AlreadyExists(String),
InvalidInput(String),
TimedOut(String),
UnexpectedEof(String),
Other(String),
}
impl IoErrorGenerationMode {
pub fn error_kind(&self) -> io::ErrorKind {
match self {
IoErrorGenerationMode::NotFound(_) => io::ErrorKind::NotFound,
IoErrorGenerationMode::PermissionDenied(_) => io::ErrorKind::PermissionDenied,
IoErrorGenerationMode::ConnectionRefused(_) => io::ErrorKind::ConnectionRefused,
IoErrorGenerationMode::ConnectionReset(_) => io::ErrorKind::ConnectionReset,
IoErrorGenerationMode::BrokenPipe(_) => io::ErrorKind::BrokenPipe,
IoErrorGenerationMode::AlreadyExists(_) => io::ErrorKind::AlreadyExists,
IoErrorGenerationMode::InvalidInput(_) => io::ErrorKind::InvalidInput,
IoErrorGenerationMode::TimedOut(_) => io::ErrorKind::TimedOut,
IoErrorGenerationMode::UnexpectedEof(_) => io::ErrorKind::UnexpectedEof,
IoErrorGenerationMode::Other(_) => io::ErrorKind::Other,
}
}
pub fn message(&self) -> &str {
match self {
IoErrorGenerationMode::NotFound(msg)
| IoErrorGenerationMode::PermissionDenied(msg)
| IoErrorGenerationMode::ConnectionRefused(msg)
| IoErrorGenerationMode::ConnectionReset(msg)
| IoErrorGenerationMode::BrokenPipe(msg)
| IoErrorGenerationMode::AlreadyExists(msg)
| IoErrorGenerationMode::InvalidInput(msg)
| IoErrorGenerationMode::TimedOut(msg)
| IoErrorGenerationMode::UnexpectedEof(msg)
| IoErrorGenerationMode::Other(msg) => msg,
}
}
}
impl Select for IoErrorGenerationMode {
fn options() -> Vec<Self> {
vec![]
}
fn labels() -> Vec<String> {
vec![
"NotFound".to_string(),
"PermissionDenied".to_string(),
"ConnectionRefused".to_string(),
"ConnectionReset".to_string(),
"BrokenPipe".to_string(),
"AlreadyExists".to_string(),
"InvalidInput".to_string(),
"TimedOut".to_string(),
"UnexpectedEof".to_string(),
"Other".to_string(),
]
}
fn from_label(label: &str) -> Option<Self> {
match label {
"NotFound" => Some(IoErrorGenerationMode::NotFound(String::new())),
"PermissionDenied" => Some(IoErrorGenerationMode::PermissionDenied(String::new())),
"ConnectionRefused" => Some(IoErrorGenerationMode::ConnectionRefused(String::new())),
"ConnectionReset" => Some(IoErrorGenerationMode::ConnectionReset(String::new())),
"BrokenPipe" => Some(IoErrorGenerationMode::BrokenPipe(String::new())),
"AlreadyExists" => Some(IoErrorGenerationMode::AlreadyExists(String::new())),
"InvalidInput" => Some(IoErrorGenerationMode::InvalidInput(String::new())),
"TimedOut" => Some(IoErrorGenerationMode::TimedOut(String::new())),
"UnexpectedEof" => Some(IoErrorGenerationMode::UnexpectedEof(String::new())),
"Other" => Some(IoErrorGenerationMode::Other(String::new())),
_ => None,
}
}
}
crate::default_style!(IoErrorGenerationMode => IoErrorGenerationModeStyle);
impl Prompt for IoErrorGenerationMode {
fn prompt() -> Option<&'static str> {
Some("Select the type of IO error to create for testing:")
}
}
impl Elicitation for IoErrorGenerationMode {
type Style = IoErrorGenerationModeStyle;
async fn elicit<C: ElicitCommunicator>(communicator: &C) -> ElicitResult<Self> {
let params = mcp::select_params(
Self::prompt().unwrap_or("Select an option:"),
&Self::labels(),
);
let result = communicator
.call_tool(rmcp::model::CallToolRequestParams {
meta: None,
name: mcp::tool_names::elicit_select().into(),
arguments: Some(params),
task: None,
})
.await?;
let value = mcp::extract_value(result)?;
let label = mcp::parse_string(value)?;
let selected = Self::from_label(&label).ok_or_else(|| {
ElicitError::new(ElicitErrorKind::ParseError(
"Invalid IO error kind".to_string(),
))
})?;
let message = String::elicit(communicator).await?;
let mode = match selected {
IoErrorGenerationMode::NotFound(_) => IoErrorGenerationMode::NotFound(message),
IoErrorGenerationMode::PermissionDenied(_) => {
IoErrorGenerationMode::PermissionDenied(message)
}
IoErrorGenerationMode::ConnectionRefused(_) => {
IoErrorGenerationMode::ConnectionRefused(message)
}
IoErrorGenerationMode::ConnectionReset(_) => {
IoErrorGenerationMode::ConnectionReset(message)
}
IoErrorGenerationMode::BrokenPipe(_) => IoErrorGenerationMode::BrokenPipe(message),
IoErrorGenerationMode::AlreadyExists(_) => {
IoErrorGenerationMode::AlreadyExists(message)
}
IoErrorGenerationMode::InvalidInput(_) => IoErrorGenerationMode::InvalidInput(message),
IoErrorGenerationMode::TimedOut(_) => IoErrorGenerationMode::TimedOut(message),
IoErrorGenerationMode::UnexpectedEof(_) => {
IoErrorGenerationMode::UnexpectedEof(message)
}
IoErrorGenerationMode::Other(_) => IoErrorGenerationMode::Other(message),
};
Ok(mode)
}
}
#[derive(Debug, Clone)]
pub struct IoErrorGenerator {
mode: IoErrorGenerationMode,
}
impl IoErrorGenerator {
pub fn new(mode: IoErrorGenerationMode) -> Self {
Self { mode }
}
pub fn mode(&self) -> &IoErrorGenerationMode {
&self.mode
}
}
impl Generator for IoErrorGenerator {
type Target = io::Error;
fn generate(&self) -> Self::Target {
io::Error::new(self.mode.error_kind(), self.mode.message())
}
}
crate::default_style!(io::Error => IoErrorStyle);
impl Prompt for io::Error {
fn prompt() -> Option<&'static str> {
Some("Create an IO error for testing:")
}
}
impl Elicitation for io::Error {
type Style = IoErrorStyle;
async fn elicit<C: ElicitCommunicator>(communicator: &C) -> ElicitResult<Self> {
tracing::debug!("Eliciting io::Error for testing");
let mode = IoErrorGenerationMode::elicit(communicator).await?;
let generator = IoErrorGenerator::new(mode);
Ok(generator.generate())
}
}
#[cfg(feature = "serde_json")]
mod json_error {
use super::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum JsonErrorGenerationMode {
SyntaxError,
EofWhileParsing,
InvalidNumber,
InvalidEscape,
InvalidUnicode,
}
impl Select for JsonErrorGenerationMode {
fn options() -> Vec<Self> {
vec![
JsonErrorGenerationMode::SyntaxError,
JsonErrorGenerationMode::EofWhileParsing,
JsonErrorGenerationMode::InvalidNumber,
JsonErrorGenerationMode::InvalidEscape,
JsonErrorGenerationMode::InvalidUnicode,
]
}
fn labels() -> Vec<String> {
vec![
"Syntax Error".to_string(),
"EOF While Parsing".to_string(),
"Invalid Number".to_string(),
"Invalid Escape".to_string(),
"Invalid Unicode".to_string(),
]
}
fn from_label(label: &str) -> Option<Self> {
match label {
"Syntax Error" => Some(JsonErrorGenerationMode::SyntaxError),
"EOF While Parsing" => Some(JsonErrorGenerationMode::EofWhileParsing),
"Invalid Number" => Some(JsonErrorGenerationMode::InvalidNumber),
"Invalid Escape" => Some(JsonErrorGenerationMode::InvalidEscape),
"Invalid Unicode" => Some(JsonErrorGenerationMode::InvalidUnicode),
_ => None,
}
}
}
crate::default_style!(JsonErrorGenerationMode => JsonErrorGenerationModeStyle);
impl Prompt for JsonErrorGenerationMode {
fn prompt() -> Option<&'static str> {
Some("Select the type of JSON error to create for testing:")
}
}
impl Elicitation for JsonErrorGenerationMode {
type Style = JsonErrorGenerationModeStyle;
async fn elicit<C: ElicitCommunicator>(communicator: &C) -> ElicitResult<Self> {
let labels = Self::labels();
let params = mcp::select_params(Self::prompt().unwrap_or("Select an option:"), &labels);
let result = communicator
.call_tool(rmcp::model::CallToolRequestParams {
meta: None,
name: mcp::tool_names::elicit_select().into(),
arguments: Some(params),
task: None,
})
.await?;
let value = mcp::extract_value(result)?;
let label = mcp::parse_string(value)?;
Self::from_label(&label).ok_or_else(|| {
ElicitError::new(ElicitErrorKind::ParseError(
"Invalid JSON error kind".to_string(),
))
})
}
}
#[derive(Debug, Clone, Copy)]
pub struct JsonErrorGenerator {
mode: JsonErrorGenerationMode,
}
impl JsonErrorGenerator {
pub fn new(mode: JsonErrorGenerationMode) -> Self {
Self { mode }
}
pub fn mode(&self) -> JsonErrorGenerationMode {
self.mode
}
}
impl Generator for JsonErrorGenerator {
type Target = serde_json::Error;
fn generate(&self) -> Self::Target {
let invalid_json = match self.mode {
JsonErrorGenerationMode::SyntaxError => "{invalid}",
JsonErrorGenerationMode::EofWhileParsing => "{\"key\":",
JsonErrorGenerationMode::InvalidNumber => "{\"num\": 1e999999}",
JsonErrorGenerationMode::InvalidEscape => r#"{"str": "\x"}"#,
JsonErrorGenerationMode::InvalidUnicode => r#"{"str": "\uDEAD"}"#,
};
serde_json::from_str::<serde_json::Value>(invalid_json)
.expect_err("Invalid JSON should always error")
}
}
crate::default_style!(serde_json::Error => JsonErrorStyle);
impl Prompt for serde_json::Error {
fn prompt() -> Option<&'static str> {
Some("Create a JSON parsing error for testing:")
}
}
impl Elicitation for serde_json::Error {
type Style = JsonErrorStyle;
async fn elicit<C: ElicitCommunicator>(communicator: &C) -> ElicitResult<Self> {
tracing::debug!("Eliciting serde_json::Error for testing");
let mode = JsonErrorGenerationMode::elicit(communicator).await?;
let generator = JsonErrorGenerator::new(mode);
Ok(generator.generate())
}
}
}
#[cfg(feature = "serde_json")]
pub use json_error::{JsonErrorGenerationMode, JsonErrorGenerator};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_io_error_generation() {
let mode = IoErrorGenerationMode::NotFound("config.toml".to_string());
let generator = IoErrorGenerator::new(mode);
let error = generator.generate();
assert_eq!(error.kind(), io::ErrorKind::NotFound);
assert!(error.to_string().contains("config.toml"));
}
#[test]
fn test_io_error_kinds() {
let modes = vec![
IoErrorGenerationMode::PermissionDenied("test".to_string()),
IoErrorGenerationMode::ConnectionRefused("test".to_string()),
IoErrorGenerationMode::BrokenPipe("test".to_string()),
];
for mode in modes {
let generator = IoErrorGenerator::new(mode.clone());
let error = generator.generate();
assert_eq!(error.kind(), mode.error_kind());
}
}
#[cfg(feature = "serde_json")]
#[test]
fn test_json_error_generation() {
let mode = JsonErrorGenerationMode::SyntaxError;
let generator = JsonErrorGenerator::new(mode);
let error = generator.generate();
assert!(!error.to_string().is_empty());
}
#[cfg(feature = "serde_json")]
#[test]
fn test_json_error_kinds() {
let modes = vec![
JsonErrorGenerationMode::SyntaxError,
JsonErrorGenerationMode::EofWhileParsing,
JsonErrorGenerationMode::InvalidNumber,
];
for mode in modes {
let generator = JsonErrorGenerator::new(mode);
let error = generator.generate();
assert!(!error.to_string().is_empty());
}
}
}