use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::error::{Result, SubstrateError};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SkillDescriptor {
pub name: String,
pub description: String,
pub input_schema: Value,
pub output_schema: Value,
}
pub trait SkillHandler: Send + Sync {
fn invoke(&self, input: Value) -> Result<Value>;
}
pub trait SkillPort: Send + Sync {
fn invoke(&self, name: &str, input: Value) -> Result<Value>;
fn list_skills(&self) -> Vec<SkillDescriptor>;
}
pub trait ToolRegistry: Send + Sync {
fn register(
&mut self,
descriptor: SkillDescriptor,
handler: Box<dyn SkillHandler>,
) -> Result<()>;
fn lookup(&self, name: &str) -> Option<&SkillDescriptor>;
fn list(&self) -> Vec<SkillDescriptor>;
fn validate_input(&self, name: &str, input: &Value) -> Result<()>;
}
pub fn validate_json_schema(value: &Value, schema: &Value) -> Result<()> {
let Some(schema_obj) = schema.as_object() else {
return Err(SubstrateError::SchemaValidation(
"schema must be a JSON object".into(),
));
};
if let Some(expected_type) = schema_obj.get("type").and_then(|t| t.as_str()) {
let matches = match expected_type {
"object" => value.is_object(),
"array" => value.is_array(),
"string" => value.is_string(),
"number" => value.is_number(),
"integer" => value.as_i64().is_some(),
"boolean" => value.is_boolean(),
"null" => value.is_null(),
other => {
return Err(SubstrateError::SchemaValidation(format!(
"unsupported schema type: {other}"
)));
}
};
if !matches {
return Err(SubstrateError::SchemaValidation(format!(
"expected type {expected_type}, got {value}"
)));
}
}
if let Some(required) = schema_obj.get("required").and_then(|r| r.as_array()) {
let obj = value
.as_object()
.ok_or_else(|| SubstrateError::SchemaValidation("value must be an object".into()))?;
for key in required {
let key_str = key.as_str().ok_or_else(|| {
SubstrateError::SchemaValidation("required entry must be a string".into())
})?;
if !obj.contains_key(key_str) {
return Err(SubstrateError::SchemaValidation(format!(
"missing required field: {key_str}"
)));
}
}
}
if let (Some(props), Some(obj)) = (
schema_obj.get("properties").and_then(|p| p.as_object()),
value.as_object(),
) {
for (key, prop_schema) in props {
if let Some(field_value) = obj.get(key) {
validate_json_schema(field_value, prop_schema)?;
}
}
}
Ok(())
}