use iri_s::IriS;
use rmcp::{model::CallToolResult, model::Content};
use rudof_rdf::rdf_impl::ReaderMode;
use std::str::FromStr;
pub type ToolResult<T> = Result<T, ToolExecutionError>;
#[derive(Debug)]
pub struct ToolExecutionError {
pub message: String,
pub hint: Option<String>,
}
impl ToolExecutionError {
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
hint: None,
}
}
pub fn with_hint(message: impl Into<String>, hint: impl Into<String>) -> Self {
Self {
message: message.into(),
hint: Some(hint.into()),
}
}
pub fn into_call_tool_result(self) -> CallToolResult {
let text = if let Some(hint) = self.hint {
format!("{}\n\nHint: {}", self.message, hint)
} else {
self.message
};
CallToolResult::error(vec![Content::text(text)])
}
}
impl std::fmt::Display for ToolExecutionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message)
}
}
impl std::error::Error for ToolExecutionError {}
pub fn parse_optional_format<F>(format: Option<&str>, format_name: &str, valid_values: &str) -> ToolResult<F>
where
F: FromStr + Default,
F::Err: std::fmt::Display,
{
match format {
Some(s) => F::from_str(s).map_err(|e| {
ToolExecutionError::with_hint(
format!("Invalid {}: {}", format_name, e),
format!("Supported values: {}", valid_values),
)
}),
None => Ok(F::default()),
}
}
#[allow(dead_code)]
pub fn parse_required_format<F>(format: &str, format_name: &str, valid_values: &str) -> ToolResult<F>
where
F: FromStr,
F::Err: std::fmt::Display,
{
F::from_str(format).map_err(|e| {
ToolExecutionError::with_hint(
format!("Invalid {}: {}", format_name, e),
format!("Supported values: {}", valid_values),
)
})
}
pub fn parse_optional_iri(iri: Option<&str>, field_name: &str) -> ToolResult<Option<IriS>> {
match iri {
Some(s) => IriS::from_str(s).map(Some).map_err(|e| {
ToolExecutionError::with_hint(
format!("Invalid {}: {}", field_name, e),
"Provide a valid absolute IRI (e.g., 'http://example.org/base/')",
)
}),
None => Ok(None),
}
}
pub fn parse_optional_reader_mode(mode: Option<&str>) -> ToolResult<ReaderMode> {
match mode {
Some(s) => ReaderMode::from_str(s).map_err(|e| {
ToolExecutionError::with_hint(format!("Invalid reader mode: {}", e), "Supported values: strict, lax")
}),
None => Ok(ReaderMode::Strict),
}
}
pub const RDF_FORMATS: &str = "turtle, ntriples, rdfxml, jsonld, trig, nquads, n3";
pub const SHEX_FORMATS: &str =
"shexc, shexj, turtle, ntriples, rdfxml, trig, n3, nquads, json, jsonld, internal, simple";
pub const SHACL_FORMATS: &str = "turtle, ntriples, rdfxml, jsonld, trig, n3, nquads, internal";
pub const SHAPEMAP_FORMATS: &str = "compact, json, internal, details, csv";
pub const IMAGE_FORMATS: &str = "svg, png";
pub const SPARQL_RESULT_FORMATS: &str = "internal, turtle, ntriples, json-ld, rdf-xml, csv, trig, n3, nquads";
pub const SHEX_RESULT_FORMATS: &str = "compact, details, json, csv, turtle, ntriples, rdfxml, trig, n3, nquads";
pub const SHACL_RESULT_FORMATS: &str =
"compact, details, minimal, json, csv, turtle, ntriples, rdfxml, trig, n3, nquads";
pub const READER_MODES: &str = "strict, lax";
pub const NODE_INFO_MODES: &str = "outgoing, incoming, both";
pub const SHEX_SORT_BY_MODES: &str = "node, shape, status, details";
pub const SHACL_SORT_BY_MODES: &str = "severity, node, component, value, path, sourceshape, details";