use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Tool {
name: String,
description: String,
parameters: Value,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
triggers: Vec<String>,
}
impl Tool {
#[must_use]
pub fn new<N, D>(name: N, description: D, parameters: Value) -> Self
where
N: Into<String>,
D: Into<String>,
{
Tool {
name: name.into(),
description: description.into(),
parameters,
triggers: Vec::new(),
}
}
#[must_use]
pub fn with_trigger<T>(mut self, trigger: T) -> Self
where
T: Into<String>,
{
self.triggers.push(trigger.into());
self
}
#[inline]
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[inline]
#[must_use]
pub fn description(&self) -> &str {
&self.description
}
#[inline]
#[must_use]
pub fn parameters(&self) -> &Value {
&self.parameters
}
#[inline]
#[must_use]
pub fn triggers(&self) -> &[String] {
&self.triggers
}
#[cfg(feature = "schemars")]
#[cfg_attr(docsrs, doc(cfg(feature = "schemars")))]
#[must_use]
pub fn of<T>(name: &str, description: &str) -> Self
where
T: schemars::JsonSchema,
{
let parameters = schemars::schema_for!(T).to_value();
Tool::new(name, description, parameters)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_wire_form_is_flat_and_compact() {
let tool = Tool::new("set_light", "Turn a light on or off", Value::Null);
let wire = serde_json::to_string(&tool).expect("a Tool always serialises");
assert_eq!(
wire,
r#"{"name":"set_light","description":"Turn a light on or off","parameters":null}"#
);
}
#[test]
fn triggers_appear_only_when_present() {
let tool = Tool::new("set_light", "Turn a light on or off", Value::Null)
.with_trigger("light")
.with_trigger("lamp");
let wire = serde_json::to_string(&tool).expect("a Tool always serialises");
assert!(wire.ends_with(r#""triggers":["light","lamp"]}"#));
}
#[test]
fn a_declaration_round_trips() {
let tool = Tool::new(
"ping",
"Check a host",
serde_json::json!({ "type": "object" }),
)
.with_trigger("ping");
let wire = serde_json::to_string(&tool).expect("a Tool always serialises");
let back: Tool = serde_json::from_str(&wire).expect("its own output parses");
assert_eq!(back, tool);
}
#[test]
fn a_missing_triggers_key_deserialises_as_none() {
let back: Tool = serde_json::from_str(r#"{"name":"a","description":"b","parameters":{}}"#)
.expect("the flat form parses");
assert!(back.triggers().is_empty());
}
}