use crate::service::{errors::*, mcp_service::RudofMcpService};
use rmcp::{
ErrorData as McpError,
handler::server::wrapper::Parameters,
model::{CallToolResult, Content},
};
use rudof_lib::formats::{InputSpec, ShExFormat};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::io::Cursor;
use std::str::FromStr;
use super::helpers::*;
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct ShowShexRequest {
pub schema: String,
pub schema_format: Option<String>,
pub base_schema: Option<String>,
pub shape: Option<String>,
pub result_schema_format: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct ShowShexResponse {
pub results: String,
pub result_format: String,
pub result_size_bytes: usize,
}
pub async fn show_shex_impl(
service: &RudofMcpService,
Parameters(ShowShexRequest {
schema,
schema_format,
base_schema,
shape,
result_schema_format,
}): Parameters<ShowShexRequest>,
) -> Result<CallToolResult, McpError> {
let mut rudof = service.rudof.lock().await;
let shex_format_hint = format!("Supported values: {}", SHEX_FORMATS);
let parsed_schema = match parse_value_with_hint(
&schema,
"schema",
"Provide valid schema content, URL, or file path",
InputSpec::from_str,
) {
Ok(value) => value,
Err(e) => return Ok(e.into_call_tool_result()),
};
let parsed_schema_format = match parse_optional_value_with_hint(
schema_format.as_deref(),
"schema format",
&shex_format_hint,
ShExFormat::from_str,
) {
Ok(value) => value,
Err(e) => return Ok(e.into_call_tool_result()),
};
let parsed_result_format = match parse_optional_value_with_hint(
result_schema_format.as_deref(),
"result schema format",
&shex_format_hint,
ShExFormat::from_str,
) {
Ok(value) => value,
Err(e) => return Ok(e.into_call_tool_result()),
};
if let Some(fmt) = &parsed_schema_format
&& !matches!(fmt, ShExFormat::ShExC | ShExFormat::ShExJ)
{
return Ok(unsupported_format_error(
"ShEx schema input",
schema_format.as_deref().unwrap_or(""),
SHEX_INPUT_FORMATS_SUPPORTED,
)
.into_call_tool_result());
}
if let Some(fmt) = &parsed_result_format
&& !matches!(
fmt,
ShExFormat::ShExC | ShExFormat::ShExJ | ShExFormat::Json | ShExFormat::JsonLd | ShExFormat::Internal
)
{
return Ok(unsupported_format_error(
"ShEx schema output",
result_schema_format.as_deref().unwrap_or(""),
SHEX_OUTPUT_FORMATS_SUPPORTED,
)
.into_call_tool_result());
}
let mut shex_schema_loading = rudof.load_shex_schema(&parsed_schema);
if let Some(base_schema) = base_schema.as_deref() {
shex_schema_loading = shex_schema_loading.with_base(base_schema);
}
if let Some(schema_format) = &parsed_schema_format {
shex_schema_loading = shex_schema_loading.with_shex_schema_format(schema_format);
}
if let Err(e) = shex_schema_loading.execute() {
return Ok(ToolExecutionError::with_hint(
format!("Failed to parse ShEx schema: {}", e),
"Check the schema content and schema_format parameter",
)
.into_call_tool_result());
}
let mut output_buffer = Cursor::new(Vec::new());
let mut serialization = rudof.serialize_shex_schema(&mut output_buffer);
if let Some(result_schema_format) = &parsed_result_format {
serialization = serialization.with_result_shex_format(result_schema_format);
}
if let Some(shape_selector) = shape.as_deref() {
serialization = serialization.with_shape(shape_selector);
}
if let Err(e) = serialization
.with_show_schema(true)
.with_show_dependencies(true)
.with_show_statistics(true)
.execute()
{
return Ok(ToolExecutionError::with_hint(
format!("Failed to serialize ShEx schema: {}", e),
"Try a different result_schema_format or verify the shape selector",
)
.into_call_tool_result());
}
let output_bytes = output_buffer.into_inner();
let output_str = String::from_utf8(output_bytes).map_err(|e| {
internal_error(
"Conversion error",
e.to_string(),
Some(json!({"operation":"show_shex_impl","phase":"utf8_conversion"})),
)
})?;
let result_size_bytes = output_str.len();
let result_format_str = if let Some(fmt) = parsed_result_format {
fmt.to_string()
} else {
"shexc".to_string()
};
let response = ShowShexResponse {
results: output_str.clone(),
result_format: result_format_str.clone(),
result_size_bytes,
};
let structured = serialize_structured(&response, "show_shex_impl")?;
let summary = format!(
"ShEx schema serialized.\nResult format: {}\nResult size: {} bytes",
result_format_str, result_size_bytes
);
let preview_language = match response.result_format.to_lowercase().as_str() {
"json" | "jsonld" | "shexj" => "json",
"turtle" | "n3" => "turtle",
"ntriples" | "nquads" => "ntriples",
"rdfxml" => "xml",
"trig" => "trig",
"shexc" => "shex",
_ => "text",
};
let results_preview = code_block_preview(preview_language, &output_str, DEFAULT_CONTENT_PREVIEW_CHARS);
let mut result = CallToolResult::success(vec![
Content::text(summary),
Content::text(format!("## Results Preview\n\n{}", results_preview)),
]);
result.structured_content = Some(structured);
Ok(result)
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct CheckShexRequest {
pub schema: String,
pub schema_format: Option<String>,
pub base_schema: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct CheckShexResponse {
pub result: String,
}
pub async fn check_shex_impl(
service: &RudofMcpService,
Parameters(CheckShexRequest {
schema,
schema_format,
base_schema,
}): Parameters<CheckShexRequest>,
) -> Result<CallToolResult, McpError> {
let rudof = service.rudof.lock().await;
let shex_format_hint = format!("Supported values: {}", SHEX_FORMATS);
let parsed_schema = match parse_value_with_hint(
&schema,
"schema",
"Provide valid schema content, URL, or file path",
InputSpec::from_str,
) {
Ok(value) => value,
Err(e) => return Ok(e.into_call_tool_result()),
};
let parsed_schema_format = match parse_optional_value_with_hint(
schema_format.as_deref(),
"schema format",
&shex_format_hint,
ShExFormat::from_str,
) {
Ok(value) => value,
Err(e) => return Ok(e.into_call_tool_result()),
};
if let Some(fmt) = &parsed_schema_format
&& !matches!(fmt, ShExFormat::ShExC | ShExFormat::ShExJ)
{
return Ok(unsupported_format_error(
"ShEx schema input",
schema_format.as_deref().unwrap_or(""),
SHEX_INPUT_FORMATS_SUPPORTED,
)
.into_call_tool_result());
}
let mut output_buffer = Cursor::new(Vec::new());
let mut checking = rudof.check_shex_schema(&parsed_schema, &mut output_buffer);
if let Some(base_schema) = base_schema.as_deref() {
checking = checking.with_base(base_schema);
}
if let Some(schema_format) = &parsed_schema_format {
checking = checking.with_shex_schema_format(schema_format);
}
if let Err(e) = checking.execute() {
return Ok(ToolExecutionError::with_hint(
format!("ShEx schema is not well-formed: {}", e),
"Review the schema syntax and correct the reported errors",
)
.into_call_tool_result());
}
let output_bytes = output_buffer.into_inner();
let output_str = String::from_utf8(output_bytes).map_err(|e| {
internal_error(
"Conversion error",
e.to_string(),
Some(json!({"operation":"check_shex_impl","phase":"utf8_conversion"})),
)
})?;
let response = CheckShexResponse {
result: output_str.clone(),
};
let structured = serialize_structured(&response, "check_shex_impl")?;
let result_size_chars = output_str.chars().count();
let summary = format!("ShEx schema checked.\nResult size: {} chars", result_size_chars);
let results_preview = code_block_preview("text", &output_str, DEFAULT_CONTENT_PREVIEW_CHARS);
let mut result = CallToolResult::success(vec![
Content::text(summary),
Content::text(format!("## Results Preview\n\n{}", results_preview)),
]);
result.structured_content = Some(structured);
Ok(result)
}