use crate::service::{errors::*, mcp_service::RudofMcpService};
use iri_s::IriS;
use rmcp::{
ErrorData as McpError,
handler::server::wrapper::Parameters,
model::{CallToolResult, Content},
};
use rudof_lib::{
InputSpec, RudofConfig, result_shex_validation_format::ResultShExValidationFormat, shapemap_format::ShapeMapFormat,
shex::validate_shex, shex_format::ShExFormat, sort_by_result_shape_map::SortByResultShapeMap,
};
use rudof_rdf::rdf_impl::ReaderMode;
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 ValidateShexRequest {
pub schema: String,
pub schema_format: Option<String>,
pub base_schema: Option<String>,
pub base_data: Option<String>,
pub reader_mode: Option<String>,
pub maybe_node: Option<String>,
pub maybe_shape: Option<String>,
pub shapemap: Option<String>,
pub shapemap_format: Option<String>,
pub result_format: Option<String>,
pub sort_by: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct ValidateShexResponse {
pub results: String,
pub result_format: String,
pub sort_by: String,
pub result_size_bytes: usize,
pub result_lines: usize,
}
pub async fn validate_shex_impl(
service: &RudofMcpService,
Parameters(ValidateShexRequest {
schema,
schema_format,
base_schema,
base_data,
reader_mode,
maybe_node,
maybe_shape,
shapemap,
shapemap_format,
result_format,
sort_by,
}): Parameters<ValidateShexRequest>,
) -> Result<CallToolResult, McpError> {
let result_format_str = result_format.clone().unwrap_or_else(|| "compact".to_string());
let sort_by_str = sort_by.clone().unwrap_or_else(|| "node".to_string());
let shcema_spec = Some(InputSpec::Str(schema.clone()));
let parsed_schema_format: Option<ShExFormat> = match schema_format.as_deref() {
Some(s) => match ShExFormat::from_str(s) {
Ok(fmt) => Some(fmt),
Err(e) => {
return Ok(ToolExecutionError::with_hint(
format!("Invalid schema format '{}': {}", s, e),
format!("Supported formats: {}", SHEX_FORMATS),
)
.into_call_tool_result());
},
},
None => None,
};
let parsed_base_schema: Option<IriS> = match base_schema.as_deref() {
Some(s) => match IriS::from_str(s) {
Ok(iri) => Some(iri),
Err(e) => {
return Ok(ToolExecutionError::with_hint(
format!("Invalid base IRI '{}': {}", s, e),
"Provide a valid absolute IRI (e.g., 'http://example.org/base/')",
)
.into_call_tool_result());
},
},
None => None,
};
let parsed_base_data: Option<IriS> = match base_data.as_deref() {
Some(s) => match IriS::from_str(s) {
Ok(iri) => Some(iri),
Err(e) => {
return Ok(ToolExecutionError::with_hint(
format!("Invalid base_data IRI '{}': {}", s, e),
"Provide a valid absolute IRI (e.g., 'http://example.org/data/')",
)
.into_call_tool_result());
},
},
None => None,
};
let parsed_reader_mode: ReaderMode = match reader_mode.as_deref() {
Some(s) => match ReaderMode::from_str(s) {
Ok(mode) => mode,
Err(e) => {
return Ok(ToolExecutionError::with_hint(
format!("Invalid reader mode '{}': {}", s, e),
format!("Supported modes: {}", READER_MODES),
)
.into_call_tool_result());
},
},
None => ReaderMode::Strict,
};
let shapemap_spec: Option<InputSpec> = shapemap.map(|s| InputSpec::Str(s.clone()));
let parsed_shapemap_format: ShapeMapFormat = match shapemap_format.as_deref() {
Some(s) => match ShapeMapFormat::from_str(s) {
Ok(fmt) => fmt,
Err(e) => {
return Ok(ToolExecutionError::with_hint(
format!("Invalid shapemap format '{}': {}", s, e),
format!("Supported formats: {}", SHAPEMAP_FORMATS),
)
.into_call_tool_result());
},
},
None => ShapeMapFormat::Compact,
};
let parsed_result_format: ResultShExValidationFormat = match result_format.as_deref() {
Some(s) => match ResultShExValidationFormat::from_str(s) {
Ok(fmt) => fmt,
Err(e) => {
return Ok(ToolExecutionError::with_hint(
format!("Invalid result format '{}': {}", s, e),
format!("Supported formats: {}", SHEX_RESULT_FORMATS),
)
.into_call_tool_result());
},
},
None => ResultShExValidationFormat::Compact,
};
let parsed_sort_by: SortByResultShapeMap = match sort_by.as_deref() {
Some(s) => match SortByResultShapeMap::from_str(s) {
Ok(sort) => sort,
Err(e) => {
return Ok(ToolExecutionError::with_hint(
format!("Invalid sort order '{}': {}", s, e),
format!("Supported values: {}", SHEX_SORT_BY_MODES),
)
.into_call_tool_result());
},
},
None => SortByResultShapeMap::Node,
};
let rudof_config = RudofConfig::new().unwrap();
let mut rudof = service.rudof.lock().await;
let mut output_buffer = Cursor::new(Vec::new());
validate_shex(
&mut rudof,
&shcema_spec,
&parsed_schema_format,
&parsed_base_schema,
&parsed_base_data,
&parsed_reader_mode,
&maybe_node,
&maybe_shape,
&shapemap_spec,
&parsed_shapemap_format,
&parsed_result_format,
&parsed_sort_by,
&rudof_config,
&mut output_buffer,
)
.map_err(|e| {
internal_error(
"Validation failed",
e.to_string(),
Some(json!({"operation":"validate_shex_impl", "phase":"validate_shex"})),
)
})?;
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":"validate_shex_impl", "phase":"utf8_conversion"})),
)
})?;
let result_size_bytes = output_str.len();
let result_lines = output_str.lines().count();
let response = ValidateShexResponse {
results: output_str.to_string(),
result_format: result_format_str.clone(),
sort_by: sort_by_str.clone(),
result_size_bytes,
result_lines,
};
let structured = serde_json::to_value(&response).map_err(|e| {
internal_error(
"Serialization error",
e.to_string(),
Some(json!({"operation":"validate_shex_impl", "phase":"serialize_response"})),
)
})?;
let mut summary = format!(
"# ShEx Validation Results\n\n\
**Result Format:** {}\n\
**Sort By:** {}\n\
**Result Size:** {} bytes\n\
**Result Lines:** {}\n",
result_format_str, sort_by_str, result_size_bytes, result_lines
);
if let Some(node) = &maybe_node {
summary.push_str(&format!("**Node:** {}\n", node));
}
if let Some(shape) = &maybe_shape {
summary.push_str(&format!("**Shape:** {}\n", shape));
}
let schema_display = format!("## ShEx Schema\n\n```shex\n{}\n```", schema);
let results_display = match result_format_str.to_lowercase().as_str() {
"turtle" | "n3" => format!("## Validation Results\n\n```turtle\n{}\n```", output_str),
"ntriples" | "nquads" => {
format!("## Validation Results\n\n```ntriples\n{}\n```", output_str)
},
"rdfxml" => format!("## Validation Results\n\n```xml\n{}\n```", output_str),
"trig" => format!("## Validation Results\n\n```trig\n{}\n```", output_str),
"json" | "jsonld" => format!("## Validation Results\n\n```json\n{}\n```", output_str),
_ => format!("## Validation Results\n\n```\n{}\n```", output_str),
};
let mut result = CallToolResult::success(vec![
Content::text(summary),
Content::text(schema_display),
Content::text(results_display),
]);
result.structured_content = Some(structured);
Ok(result)
}