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, parse_shape_selector, shex::parse_shex_schema, shex::serialize_current_shex_rudof,
shex::serialize_shape_current_shex_rudof, shex_format::ShExFormat,
};
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 std::time::Instant;
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 reader_mode: Option<String>,
pub shape: Option<String>,
pub result_schema_format: Option<String>,
pub compile: Option<bool>,
pub show_schema: Option<bool>,
pub show_time: Option<bool>,
pub show_dependencies: Option<bool>,
pub show_statistics: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct ShowShexResponse {
pub results: String,
pub result_format: String,
pub result_size_bytes: usize,
pub result_lines: usize,
pub elapsed_seconds: Option<f64>,
pub dependencies_included: Option<bool>,
pub statistics_included: Option<bool>,
}
pub async fn show_shex_impl(
service: &RudofMcpService,
Parameters(ShowShexRequest {
schema,
schema_format,
base_schema,
reader_mode,
shape,
result_schema_format,
compile,
show_schema,
show_time,
show_dependencies,
show_statistics,
}): Parameters<ShowShexRequest>,
) -> Result<CallToolResult, McpError> {
let parsed_schema_format: ShExFormat = match schema_format.as_deref() {
Some(s) => match ShExFormat::from_str(s) {
Ok(fmt) => fmt,
Err(e) => {
return Ok(ToolExecutionError::with_hint(
format!("Invalid schema format '{}': {}", s, e),
format!("Supported formats: {}", SHEX_FORMATS),
)
.into_call_tool_result());
},
},
None => ShExFormat::default(),
};
let parsed_result_format: ShExFormat = match result_schema_format.as_deref() {
Some(s) => match ShExFormat::from_str(s) {
Ok(fmt) => fmt,
Err(e) => {
return Ok(ToolExecutionError::with_hint(
format!("Invalid result format '{}': {}", s, e),
format!("Supported formats: {}", SHEX_FORMATS),
)
.into_call_tool_result());
},
},
None => ShExFormat::default(),
};
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_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 rudof_config = RudofConfig::new().unwrap();
let begin = Instant::now();
let mut rudof = service.rudof.lock().await;
let mut output_buffer = Cursor::new(Vec::new());
let schema_spec = InputSpec::Str(schema.clone());
if let Err(e) = parse_shex_schema(
&mut rudof,
&schema_spec,
&parsed_schema_format,
&parsed_base_schema,
&parsed_reader_mode,
&rudof_config,
) {
return Ok(ToolExecutionError::with_hint(
format!("ShEx schema parsing failed: {}", e),
"Check the schema syntax. Common issues: missing prefixes, invalid shape expressions, unbalanced braces.",
)
.into_call_tool_result());
}
if let Some(shape_selector_str) = shape {
let shape_selector = match parse_shape_selector(&shape_selector_str) {
Ok(sel) => sel,
Err(e) => {
return Ok(ToolExecutionError::with_hint(
format!("Invalid shape selector '{}': {}", shape_selector_str, e),
"Use a valid IRI or prefixed name (e.g., ':Person' or 'http://example.org/Person')",
)
.into_call_tool_result());
},
};
let formatter = shex_ast::compact::ShExFormatter::default().without_colors();
serialize_shape_current_shex_rudof(
&rudof,
&shape_selector,
&parsed_result_format,
&formatter,
&mut output_buffer,
)
.map_err(|e| {
internal_error(
"Serialization failed",
e.to_string(),
Some(json!({"operation":"show_shex_impl","phase":"serialize_shape"})),
)
})?;
} else if show_schema.unwrap_or(true) {
let formatter = shex_ast::compact::ShExFormatter::default().without_colors();
serialize_current_shex_rudof(&rudof, &parsed_result_format, &formatter, &mut output_buffer).map_err(|e| {
internal_error(
"Serialization failed",
e.to_string(),
Some(json!({"operation":"show_shex_impl","phase":"serialize_schema"})),
)
})?;
}
let mut summary = format!("# ShEx Show Results\n\n**Result Format:** {}\n", parsed_result_format);
let mut dependencies_included = false;
let mut statistics_included = false;
if compile.unwrap_or(false) && rudof.get_shex_ir().is_some() {
summary.push_str("**Compiled IR:** available\n");
if show_dependencies.unwrap_or(false) {
dependencies_included = true;
}
if show_statistics.unwrap_or(false) {
statistics_included = true;
}
}
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_lines = output_str.lines().count();
let response = ShowShexResponse {
results: output_str.clone(),
result_format: parsed_result_format.to_string(),
result_size_bytes,
result_lines,
elapsed_seconds: if show_time.unwrap_or(false) {
Some(begin.elapsed().as_secs_f64())
} else {
None
},
dependencies_included: if dependencies_included { Some(true) } else { None },
statistics_included: if statistics_included { Some(true) } else { None },
};
let structured = serde_json::to_value(&response).map_err(|e| {
internal_error(
"Serialization error",
e.to_string(),
Some(json!({"operation":"show_shex_impl","phase":"serialize_response"})),
)
})?;
let mut contents = vec![Content::text(summary)];
contents.push(Content::text(format!("## Schema\n\n```shex\n{}\n```", schema)));
contents.push(Content::text(format!("## Serialized\n\n```shex\n{}\n```", output_str)));
if let Some(seconds) = response.elapsed_seconds {
contents.insert(1, Content::text(format!("**Elapsed:** {:.03?} sec", seconds)));
}
if (dependencies_included || statistics_included)
&& let Some(shex_ir) = rudof.get_shex_ir()
{
if statistics_included {
let mut stats = String::new();
stats.push_str("## Statistics\n\n");
stats.push_str(&format!(
"Local shapes: {}/Total shapes {}\n\n",
shex_ir.local_shapes_count(),
shex_ir.total_shapes_count()
));
let extends = shex_ir.count_extends();
for (k, v) in extends.iter() {
stats.push_str(&format!("Shapes with {k} extends = {v}\n"));
}
contents.push(Content::text(stats));
}
if dependencies_included {
let mut deps = String::new();
deps.push_str("## Dependencies\n\n");
for (source, posneg, target) in shex_ir.dependencies() {
deps.push_str(&format!("{source}-{posneg}->{target}\n"));
}
contents.push(Content::text(deps));
}
}
let mut result = CallToolResult::success(contents);
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>,
pub reader_mode: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct CheckShexResponse {
pub valid: bool,
pub message: Option<String>,
pub elapsed_seconds: Option<f64>,
}
pub async fn check_shex_impl(
_service: &RudofMcpService,
Parameters(CheckShexRequest {
schema,
schema_format,
base_schema,
reader_mode,
}): Parameters<CheckShexRequest>,
) -> Result<CallToolResult, McpError> {
let parsed_schema_format: ShExFormat = match schema_format.as_deref() {
Some(s) => match ShExFormat::from_str(s) {
Ok(fmt) => fmt,
Err(e) => {
return Ok(ToolExecutionError::with_hint(
format!("Invalid schema format '{}': {}", s, e),
format!("Supported formats: {}", SHEX_FORMATS),
)
.into_call_tool_result());
},
},
None => ShExFormat::default(),
};
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_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 rudof_config = RudofConfig::new().unwrap();
let begin = Instant::now();
let mut tmp = rudof_lib::Rudof::new(&rudof_config).unwrap();
let schema_spec = InputSpec::Str(schema.clone());
match parse_shex_schema(
&mut tmp,
&schema_spec,
&parsed_schema_format,
&parsed_base_schema,
&parsed_reader_mode,
&rudof_config,
) {
Ok(_) => {
let elapsed = begin.elapsed().as_secs_f64();
let response = CheckShexResponse {
valid: true,
message: None,
elapsed_seconds: Some(elapsed),
};
let structured = serde_json::to_value(&response).map_err(|e| {
internal_error(
"Serialization error",
e.to_string(),
Some(json!({"operation":"check_shex_impl","phase":"serialize_response"})),
)
})?;
let contents = vec![Content::text("Schema is valid".to_string())];
let mut result = CallToolResult::success(contents);
result.structured_content = Some(structured);
Ok(result)
},
Err(err) => {
let elapsed = begin.elapsed().as_secs_f64();
let response = CheckShexResponse {
valid: false,
message: Some(err.to_string()),
elapsed_seconds: Some(elapsed),
};
let structured = serde_json::to_value(&response).map_err(|e| {
internal_error(
"Serialization error",
e.to_string(),
Some(json!({"operation":"check_shex_impl","phase":"serialize_response_error"})),
)
})?;
let contents = vec![Content::text(format!("Schema is NOT valid:\n{}", err))];
let mut result = CallToolResult::success(contents);
result.structured_content = Some(structured);
Ok(result)
},
}
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct ShapeInfoRequest {
pub schema: String,
pub schema_format: Option<String>,
pub base_schema: Option<String>,
pub reader_mode: Option<String>,
pub shape: String,
pub result_schema_format: Option<String>,
pub compile: Option<bool>,
pub show_time: Option<bool>,
pub show_dependencies: Option<bool>,
pub show_statistics: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct ShapeInfoResponse {
pub results: String,
pub result_format: String,
pub result_size_bytes: usize,
pub result_lines: usize,
pub elapsed_seconds: Option<f64>,
pub dependencies_included: Option<bool>,
pub statistics_included: Option<bool>,
}
pub async fn shape_info_impl(
service: &RudofMcpService,
Parameters(ShapeInfoRequest {
schema,
schema_format,
base_schema,
reader_mode,
shape,
result_schema_format,
compile,
show_time,
show_dependencies,
show_statistics,
}): Parameters<ShapeInfoRequest>,
) -> Result<CallToolResult, McpError> {
let parsed_schema_format: ShExFormat =
match parse_optional_format::<ShExFormat>(schema_format.as_deref(), "schema format", SHEX_FORMATS) {
Ok(fmt) => fmt,
Err(e) => return Ok(e.into_call_tool_result()),
};
let parsed_result_format: ShExFormat = match parse_optional_format::<ShExFormat>(
result_schema_format.as_deref(),
"result schema format",
SHEX_FORMATS,
) {
Ok(fmt) => fmt,
Err(e) => return Ok(e.into_call_tool_result()),
};
let parsed_base_schema: Option<IriS> = match parse_optional_iri(base_schema.as_deref(), "base IRI") {
Ok(iri) => iri,
Err(e) => return Ok(e.into_call_tool_result()),
};
let parsed_reader_mode: ReaderMode = match parse_optional_reader_mode(reader_mode.as_deref()) {
Ok(mode) => mode,
Err(e) => return Ok(e.into_call_tool_result()),
};
let rudof_config = RudofConfig::new().unwrap();
let begin = Instant::now();
let mut rudof = service.rudof.lock().await;
let mut output_buffer = Cursor::new(Vec::new());
let schema_spec = InputSpec::Str(schema.clone());
if let Err(e) = parse_shex_schema(
&mut rudof,
&schema_spec,
&parsed_schema_format,
&parsed_base_schema,
&parsed_reader_mode,
&rudof_config,
) {
return Ok(ToolExecutionError::with_hint(
format!("ShEx schema parsing failed: {}", e),
"Check the schema syntax. Common issues: missing prefixes, invalid shape expressions, unbalanced braces.",
)
.into_call_tool_result());
}
let shape_selector = match parse_shape_selector(&shape) {
Ok(sel) => sel,
Err(e) => {
return Ok(ToolExecutionError::with_hint(
format!("Invalid shape selector '{}': {}", shape, e),
"Use a valid IRI or prefixed name (e.g., ':Person' or 'http://example.org/Person')",
)
.into_call_tool_result());
},
};
let formatter = shex_ast::compact::ShExFormatter::default().without_colors();
serialize_shape_current_shex_rudof(
&rudof,
&shape_selector,
&parsed_result_format,
&formatter,
&mut output_buffer,
)
.map_err(|e| {
internal_error(
"Serialization failed",
e.to_string(),
Some(json!({"operation":"shape_info_impl","phase":"serialize_shape"})),
)
})?;
let mut dependencies_included = false;
let mut statistics_included = false;
if compile.unwrap_or(false) && rudof.get_shex_ir().is_some() {
if show_dependencies.unwrap_or(false) {
dependencies_included = true;
}
if show_statistics.unwrap_or(false) {
statistics_included = true;
}
}
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":"shape_info_impl","phase":"utf8_conversion"})),
)
})?;
let result_size_bytes = output_str.len();
let result_lines = output_str.lines().count();
let response = ShapeInfoResponse {
results: output_str.clone(),
result_format: parsed_result_format.to_string(),
result_size_bytes,
result_lines,
elapsed_seconds: if show_time.unwrap_or(false) {
Some(begin.elapsed().as_secs_f64())
} else {
None
},
dependencies_included: if dependencies_included { Some(true) } else { None },
statistics_included: if statistics_included { Some(true) } else { None },
};
let structured = serde_json::to_value(&response).map_err(|e| {
internal_error(
"Serialization error",
e.to_string(),
Some(json!({"operation":"shape_info_impl","phase":"serialize_response"})),
)
})?;
let mut contents = vec![Content::text(format!("## Shape {}", shape))];
contents.push(Content::text(format!(
"```
{}
```",
response.results
)));
if let Some(shex_ir) = rudof.get_shex_ir() {
if statistics_included {
let mut stats = String::new();
stats.push_str("## Statistics\n\n");
stats.push_str(&format!(
"Local shapes: {}/Total shapes {}\n\n",
shex_ir.local_shapes_count(),
shex_ir.total_shapes_count()
));
let extends = shex_ir.count_extends();
for (k, v) in extends.iter() {
stats.push_str(&format!("Shapes with {k} extends = {v}\n"));
}
contents.push(Content::text(stats));
}
if dependencies_included {
let mut deps = String::new();
deps.push_str("## Dependencies\n\n");
for (source, posneg, target) in shex_ir.dependencies() {
deps.push_str(&format!("{source}-{posneg}->{target}\n"));
}
contents.push(Content::text(deps));
}
}
let mut result = CallToolResult::success(contents);
result.structured_content = Some(structured);
Ok(result)
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct ConvertShexRequest {
pub schema: String,
pub schema_format: Option<String>,
pub base_schema: Option<String>,
pub reader_mode: Option<String>,
pub result_schema_format: Option<String>,
pub show_time: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct ConvertShexResponse {
pub results: String,
pub result_format: String,
pub result_size_bytes: usize,
pub result_lines: usize,
pub elapsed_seconds: Option<f64>,
}
pub async fn convert_shex_impl(
service: &RudofMcpService,
Parameters(ConvertShexRequest {
schema,
schema_format,
base_schema,
reader_mode,
result_schema_format,
show_time,
}): Parameters<ConvertShexRequest>,
) -> Result<CallToolResult, McpError> {
let parsed_schema_format: ShExFormat =
match parse_optional_format::<ShExFormat>(schema_format.as_deref(), "schema format", SHEX_FORMATS) {
Ok(fmt) => fmt,
Err(e) => return Ok(e.into_call_tool_result()),
};
let parsed_result_format: ShExFormat = match parse_optional_format::<ShExFormat>(
result_schema_format.as_deref(),
"result schema format",
SHEX_FORMATS,
) {
Ok(fmt) => fmt,
Err(e) => return Ok(e.into_call_tool_result()),
};
let parsed_base_schema: Option<IriS> = match parse_optional_iri(base_schema.as_deref(), "base IRI") {
Ok(iri) => iri,
Err(e) => return Ok(e.into_call_tool_result()),
};
let parsed_reader_mode: ReaderMode = match parse_optional_reader_mode(reader_mode.as_deref()) {
Ok(mode) => mode,
Err(e) => return Ok(e.into_call_tool_result()),
};
let rudof_config = RudofConfig::new().unwrap();
let begin = Instant::now();
let mut rudof = service.rudof.lock().await;
let mut output_buffer = Cursor::new(Vec::new());
let schema_spec = InputSpec::Str(schema.clone());
if let Err(e) = parse_shex_schema(
&mut rudof,
&schema_spec,
&parsed_schema_format,
&parsed_base_schema,
&parsed_reader_mode,
&rudof_config,
) {
return Ok(ToolExecutionError::with_hint(
format!("ShEx schema parsing failed: {}", e),
"Check the schema syntax. Common issues: missing prefixes, invalid shape expressions, unbalanced braces.",
)
.into_call_tool_result());
}
let formatter = shex_ast::compact::ShExFormatter::default().without_colors();
serialize_current_shex_rudof(&rudof, &parsed_result_format, &formatter, &mut output_buffer).map_err(|e| {
internal_error(
"Serialization failed",
e.to_string(),
Some(json!({"operation":"convert_shex_impl","phase":"serialize_schema"})),
)
})?;
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":"convert_shex_impl","phase":"utf8_conversion"})),
)
})?;
let result_size_bytes = output_str.len();
let result_lines = output_str.lines().count();
let response = ConvertShexResponse {
results: output_str.clone(),
result_format: parsed_result_format.to_string(),
result_size_bytes,
result_lines,
elapsed_seconds: if show_time.unwrap_or(false) {
Some(begin.elapsed().as_secs_f64())
} else {
None
},
};
let structured = serde_json::to_value(&response).map_err(|e| {
internal_error(
"Serialization error",
e.to_string(),
Some(json!({"operation":"convert_shex_impl","phase":"serialize_response"})),
)
})?;
let mut contents = vec![Content::text(format!("**Result Format:** {}", parsed_result_format))];
contents.push(Content::text(format!(
"```shex
{}
```",
output_str
)));
let mut result = CallToolResult::success(contents);
result.structured_content = Some(structured);
Ok(result)
}