use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct ToolManifest {
pub tool: ToolMeta,
pub exec: ExecConfig,
pub transport: Option<TransportConfig>,
#[serde(default)]
pub parameters: Vec<ParameterDef>,
}
#[derive(Debug, Deserialize)]
pub struct ToolMeta {
pub name: String,
pub version: String,
pub description: String,
}
#[derive(Debug, Deserialize)]
pub struct ExecConfig {
pub binary: String,
}
#[derive(Debug, Deserialize)]
pub struct TransportConfig {
pub preferred: String,
pub device_required: bool,
}
#[derive(Debug, Deserialize)]
pub struct ParameterDef {
pub name: String,
#[serde(rename = "type")]
pub r#type: String,
pub description: String,
pub required: bool,
pub default: Option<serde_json::Value>,
}
#[cfg(test)]
mod tests {
use super::*;
const MINIMAL_TOML: &str = r#"
[tool]
name = "i2c_scan"
version = "1.0.0"
description = "Scan the I2C bus"
[exec]
binary = "i2c_scan.py"
[[parameters]]
name = "device"
type = "string"
description = "Device alias"
required = true
"#;
#[test]
fn manifest_parses_minimal_toml() {
let m: ToolManifest = toml::from_str(MINIMAL_TOML).expect("parse failed");
assert_eq!(m.tool.name, "i2c_scan");
assert_eq!(m.tool.version, "1.0.0");
assert_eq!(m.exec.binary, "i2c_scan.py");
assert!(m.transport.is_none());
assert_eq!(m.parameters.len(), 1);
assert_eq!(m.parameters[0].name, "device");
assert!(m.parameters[0].required);
}
const FULL_TOML: &str = r#"
[tool]
name = "pwm_set"
version = "1.0.0"
description = "Set PWM duty cycle on a pin"
[exec]
binary = "pwm_set"
[transport]
preferred = "serial"
device_required = true
[[parameters]]
name = "device"
type = "string"
description = "Device alias"
required = true
[[parameters]]
name = "pin"
type = "integer"
description = "PWM pin number"
required = true
[[parameters]]
name = "duty"
type = "integer"
description = "Duty cycle 0–100"
required = false
default = 50
"#;
#[test]
fn manifest_parses_full_toml_with_transport_and_defaults() {
let m: ToolManifest = toml::from_str(FULL_TOML).expect("parse failed");
assert_eq!(m.tool.name, "pwm_set");
let transport = m.transport.as_ref().expect("transport missing");
assert_eq!(transport.preferred, "serial");
assert!(transport.device_required);
let duty = m
.parameters
.iter()
.find(|p| p.name == "duty")
.expect("duty param missing");
assert!(!duty.required);
assert_eq!(duty.default, Some(serde_json::json!(50)));
}
#[test]
fn manifest_empty_parameters_default_to_empty_vec() {
let raw = r#"
[tool]
name = "noop"
version = "0.1.0"
description = "No-op tool"
[exec]
binary = "noop"
"#;
let m: ToolManifest = toml::from_str(raw).expect("parse failed");
assert!(m.parameters.is_empty());
}
}