pub mod cli_markdown;
#[cfg(feature = "mcp")]
pub mod mcp_markdown;
pub mod swagger;
pub use swagger::swagger_ui_router;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DocFormat {
#[default]
OpenApi,
SwaggerUi,
CliMarkdown,
McpMarkdown,
All,
}
#[derive(Debug, thiserror::Error)]
pub enum DocError {
#[error("OpenAPI serialization failed: {0}")]
Serialization(#[from] serde_json::Error),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
}
pub fn generate_docs(format: DocFormat) -> Result<String, DocError> {
match format {
DocFormat::OpenApi => {
let spec = crate::openapi::generate_openapi_spec();
Ok(serde_json::to_string_pretty(&spec)?)
}
DocFormat::SwaggerUi => Ok(generate_swagger_html()),
DocFormat::CliMarkdown => Ok(cli_markdown::generate_cli_docs()),
DocFormat::McpMarkdown => Ok(generate_mcp_markdown()),
DocFormat::All => {
let mut out = String::new();
out.push_str("# OpenAPI Specification\n\n");
out.push_str(&generate_docs(DocFormat::OpenApi)?);
out.push_str("\n\n");
out.push_str("# CLI Documentation\n\n");
out.push_str(&generate_docs(DocFormat::CliMarkdown)?);
out.push_str("\n\n");
out.push_str(&generate_docs(DocFormat::McpMarkdown)?);
out.push_str("\n\n");
out.push_str("<!-- Swagger UI: run with --format swagger or visit /swagger-ui/ for interactive docs -->\n");
Ok(out)
}
}
}
fn generate_mcp_markdown() -> String {
#[cfg(feature = "mcp")]
{
mcp_markdown::generate_mcp_docs()
}
#[cfg(not(feature = "mcp"))]
{
"<!-- MCP feature not enabled. Enable `mcp` feature to generate MCP tool documentation. -->\n"
.to_string()
}
}
fn generate_swagger_html() -> String {
r#"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>SDForge API Docs</title>
<meta http-equiv="refresh" content="0; url=/swagger-ui/">
<script>window.location.replace('/swagger-ui/');</script>
</head>
<body>
<p>Redirecting to <a href="/swagger-ui/">Swagger UI</a>...</p>
</body>
</html>"#
.to_string()
}
pub fn write_docs(format: DocFormat, output_path: &std::path::Path) -> Result<(), DocError> {
if output_path
.components()
.any(|c| c == std::path::Component::ParentDir)
{
return Err(DocError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"output path must not contain parent directory components (..)",
)));
}
let content = generate_docs(format)?;
std::fs::write(output_path, content)?;
Ok(())
}
#[cfg(test)]
mod tests;