use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::errors::{ErrorCode, ModuleError};
#[derive(Debug, Clone, Default)]
pub struct ExportOptions {
pub annotations: Option<serde_json::Value>,
pub examples: Option<serde_json::Value>,
pub name: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ExportProfile {
Mcp,
#[serde(rename = "openai")]
OpenAi,
Anthropic,
Generic,
}
#[derive(Debug)]
pub struct SchemaExporter;
impl SchemaExporter {
#[must_use]
pub fn new() -> Self {
Self
}
pub fn export(
&self,
schema: &serde_json::Value,
profile: ExportProfile,
options: Option<&ExportOptions>,
) -> Result<serde_json::Value, ModuleError> {
let mut exported = match profile {
ExportProfile::Mcp => self.export_mcp(schema)?,
ExportProfile::OpenAi => self.export_openai(schema)?,
ExportProfile::Anthropic => self.export_anthropic(schema)?,
ExportProfile::Generic => self.export_generic(schema)?,
};
if let Some(opts) = options {
if let (Some(obj), Some(name)) = (exported.as_object_mut(), &opts.name) {
obj.insert("name".to_string(), serde_json::Value::String(name.clone()));
}
if let (Some(obj), Some(annotations)) = (exported.as_object_mut(), &opts.annotations) {
if let Some(ann_obj) = annotations.as_object() {
for (k, v) in ann_obj {
obj.entry(k.clone()).or_insert_with(|| v.clone());
}
}
}
if let (Some(obj), Some(examples)) = (exported.as_object_mut(), &opts.examples) {
obj.insert("examples".to_string(), examples.clone());
}
}
Ok(exported)
}
pub fn export_serialized(
&self,
schema: &serde_json::Value,
profile: ExportProfile,
options: Option<&ExportOptions>,
) -> Result<String, ModuleError> {
let value = self.export(schema, profile, options)?;
serde_json::to_string_pretty(&value).map_err(|e| {
ModuleError::new(
ErrorCode::SchemaParseError,
format!("Failed to serialize exported schema: {e}"),
)
})
}
pub fn export_all(
&self,
loader: &super::loader::SchemaLoader,
profile: ExportProfile,
) -> Result<HashMap<String, serde_json::Value>, ModuleError> {
let mut result = HashMap::new();
for name in loader.list() {
if let Some(schema) = loader.get(name) {
let exported = self.export(schema, profile, None)?;
result.insert(name.to_string(), exported);
}
}
Ok(result)
}
pub fn export_all_serialized(
&self,
loader: &super::loader::SchemaLoader,
profile: ExportProfile,
) -> Result<HashMap<String, String>, ModuleError> {
let mut result = HashMap::new();
for name in loader.list() {
if let Some(schema) = loader.get(name) {
let exported = self.export_serialized(schema, profile, None)?;
result.insert(name.to_string(), exported);
}
}
Ok(result)
}
#[allow(clippy::unused_self)] #[allow(clippy::unnecessary_wraps)] fn export_mcp(&self, schema: &serde_json::Value) -> Result<serde_json::Value, ModuleError> {
let name = schema
.get("name")
.cloned()
.unwrap_or(serde_json::Value::Null);
let input_schema = schema
.get("input_schema")
.or_else(|| schema.get("inputSchema"))
.cloned()
.unwrap_or(serde_json::json!({}));
Ok(serde_json::json!({
"name": name,
"inputSchema": input_schema,
}))
}
#[allow(clippy::unused_self)] #[allow(clippy::unnecessary_wraps)] fn export_openai(&self, schema: &serde_json::Value) -> Result<serde_json::Value, ModuleError> {
let name = schema
.get("name")
.cloned()
.unwrap_or(serde_json::Value::Null);
let description = schema
.get("description")
.cloned()
.unwrap_or(serde_json::Value::String(String::new()));
let parameters = schema
.get("input_schema")
.or_else(|| schema.get("inputSchema"))
.or_else(|| schema.get("parameters"))
.cloned()
.unwrap_or(serde_json::json!({}));
Ok(serde_json::json!({
"type": "function",
"function": {
"name": name,
"description": description,
"parameters": parameters,
"strict": true,
}
}))
}
#[allow(clippy::unused_self)] #[allow(clippy::unnecessary_wraps)] fn export_anthropic(
&self,
schema: &serde_json::Value,
) -> Result<serde_json::Value, ModuleError> {
let name = schema
.get("name")
.cloned()
.unwrap_or(serde_json::Value::Null);
let description = schema
.get("description")
.cloned()
.unwrap_or(serde_json::Value::String(String::new()));
let input_schema = schema
.get("input_schema")
.or_else(|| schema.get("inputSchema"))
.cloned()
.unwrap_or(serde_json::json!({}));
Ok(serde_json::json!({
"name": name,
"description": description,
"input_schema": input_schema,
}))
}
#[allow(clippy::unused_self)] #[allow(clippy::unnecessary_wraps)] fn export_generic(&self, schema: &serde_json::Value) -> Result<serde_json::Value, ModuleError> {
Ok(schema.clone())
}
}
impl Default for SchemaExporter {
fn default() -> Self {
Self::new()
}
}