thulp_core/
parameter.rs

1//! Parameter types for tool definitions.
2
3use serde::{Deserialize, Serialize};
4
5/// The type of a parameter.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
7#[serde(rename_all = "lowercase")]
8pub enum ParameterType {
9    /// String parameter.
10    #[default]
11    String,
12    /// Integer parameter.
13    Integer,
14    /// Number (float) parameter.
15    Number,
16    /// Boolean parameter.
17    Boolean,
18    /// Array parameter.
19    Array,
20    /// Object parameter.
21    Object,
22}
23
24impl ParameterType {
25    /// Returns the type name as a string.
26    pub fn as_str(&self) -> &'static str {
27        match self {
28            Self::String => "string",
29            Self::Integer => "integer",
30            Self::Number => "number",
31            Self::Boolean => "boolean",
32            Self::Array => "array",
33            Self::Object => "object",
34        }
35    }
36
37    /// Checks if a JSON value matches this parameter type.
38    pub fn matches(&self, value: &serde_json::Value) -> bool {
39        match (self, value) {
40            (Self::String, serde_json::Value::String(_)) => true,
41            (Self::Integer, serde_json::Value::Number(n)) => n.is_i64() || n.is_u64(),
42            (Self::Number, serde_json::Value::Number(_)) => true,
43            (Self::Boolean, serde_json::Value::Bool(_)) => true,
44            (Self::Array, serde_json::Value::Array(_)) => true,
45            (Self::Object, serde_json::Value::Object(_)) => true,
46            _ => false,
47        }
48    }
49}
50
51/// A parameter definition for a tool.
52#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
53pub struct Parameter {
54    /// The parameter name.
55    pub name: String,
56
57    /// The parameter type.
58    #[serde(rename = "type", default)]
59    pub param_type: ParameterType,
60
61    /// Whether this parameter is required.
62    #[serde(default)]
63    pub required: bool,
64
65    /// Description of the parameter.
66    #[serde(default)]
67    pub description: String,
68
69    /// Default value for the parameter.
70    #[serde(default, skip_serializing_if = "Option::is_none")]
71    pub default: Option<serde_json::Value>,
72
73    /// Enum of allowed values.
74    #[serde(default, skip_serializing_if = "Vec::is_empty")]
75    pub enum_values: Vec<serde_json::Value>,
76}
77
78impl Parameter {
79    /// Create a new parameter with the given name.
80    pub fn new(name: impl Into<String>) -> Self {
81        Self {
82            name: name.into(),
83            param_type: ParameterType::default(),
84            required: false,
85            description: String::new(),
86            default: None,
87            enum_values: Vec::new(),
88        }
89    }
90
91    /// Create a builder for a parameter.
92    pub fn builder(name: impl Into<String>) -> ParameterBuilder {
93        ParameterBuilder::new(name)
94    }
95
96    /// Create a required string parameter.
97    pub fn required_string(name: impl Into<String>) -> Self {
98        Self {
99            name: name.into(),
100            param_type: ParameterType::String,
101            required: true,
102            description: String::new(),
103            default: None,
104            enum_values: Vec::new(),
105        }
106    }
107
108    /// Create an optional string parameter.
109    pub fn optional_string(name: impl Into<String>) -> Self {
110        Self {
111            name: name.into(),
112            param_type: ParameterType::String,
113            required: false,
114            description: String::new(),
115            default: None,
116            enum_values: Vec::new(),
117        }
118    }
119}
120
121/// Builder for [`Parameter`].
122#[derive(Debug, Default)]
123pub struct ParameterBuilder {
124    name: String,
125    param_type: ParameterType,
126    required: bool,
127    description: String,
128    default: Option<serde_json::Value>,
129    enum_values: Vec<serde_json::Value>,
130}
131
132impl ParameterBuilder {
133    /// Create a new parameter builder.
134    pub fn new(name: impl Into<String>) -> Self {
135        Self {
136            name: name.into(),
137            ..Default::default()
138        }
139    }
140
141    /// Set the parameter type.
142    pub fn param_type(mut self, param_type: ParameterType) -> Self {
143        self.param_type = param_type;
144        self
145    }
146
147    /// Set whether the parameter is required.
148    pub fn required(mut self, required: bool) -> Self {
149        self.required = required;
150        self
151    }
152
153    /// Set the parameter description.
154    pub fn description(mut self, description: impl Into<String>) -> Self {
155        self.description = description.into();
156        self
157    }
158
159    /// Set the default value.
160    pub fn default(mut self, default: serde_json::Value) -> Self {
161        self.default = Some(default);
162        self
163    }
164
165    /// Add an enum value.
166    pub fn enum_value(mut self, value: serde_json::Value) -> Self {
167        self.enum_values.push(value);
168        self
169    }
170
171    /// Build the parameter.
172    pub fn build(self) -> Parameter {
173        Parameter {
174            name: self.name,
175            param_type: self.param_type,
176            required: self.required,
177            description: self.description,
178            default: self.default,
179            enum_values: self.enum_values,
180        }
181    }
182}
183
184#[cfg(test)]
185mod tests {
186    use super::*;
187    use serde_json::json;
188
189    #[test]
190    fn parameter_type_as_str() {
191        assert_eq!(ParameterType::String.as_str(), "string");
192        assert_eq!(ParameterType::Integer.as_str(), "integer");
193        assert_eq!(ParameterType::Number.as_str(), "number");
194        assert_eq!(ParameterType::Boolean.as_str(), "boolean");
195        assert_eq!(ParameterType::Array.as_str(), "array");
196        assert_eq!(ParameterType::Object.as_str(), "object");
197    }
198
199    #[test]
200    fn parameter_type_matches_string() {
201        assert!(ParameterType::String.matches(&json!("hello")));
202        assert!(!ParameterType::String.matches(&json!(123)));
203        assert!(!ParameterType::String.matches(&json!(true)));
204    }
205
206    #[test]
207    fn parameter_type_matches_integer() {
208        assert!(ParameterType::Integer.matches(&json!(123)));
209        assert!(ParameterType::Integer.matches(&json!(-456)));
210        assert!(!ParameterType::Integer.matches(&json!(1.5)));
211        assert!(!ParameterType::Integer.matches(&json!("123")));
212    }
213
214    #[test]
215    fn parameter_type_matches_number() {
216        assert!(ParameterType::Number.matches(&json!(123)));
217        assert!(ParameterType::Number.matches(&json!(1.5)));
218        assert!(ParameterType::Number.matches(&json!(-3.14)));
219        assert!(!ParameterType::Number.matches(&json!("1.5")));
220    }
221
222    #[test]
223    fn parameter_type_matches_boolean() {
224        assert!(ParameterType::Boolean.matches(&json!(true)));
225        assert!(ParameterType::Boolean.matches(&json!(false)));
226        assert!(!ParameterType::Boolean.matches(&json!("true")));
227        assert!(!ParameterType::Boolean.matches(&json!(1)));
228    }
229
230    #[test]
231    fn parameter_type_matches_array() {
232        assert!(ParameterType::Array.matches(&json!([1, 2, 3])));
233        assert!(ParameterType::Array.matches(&json!([])));
234        assert!(!ParameterType::Array.matches(&json!({"a": 1})));
235    }
236
237    #[test]
238    fn parameter_type_matches_object() {
239        assert!(ParameterType::Object.matches(&json!({"a": 1})));
240        assert!(ParameterType::Object.matches(&json!({})));
241        assert!(!ParameterType::Object.matches(&json!([1, 2])));
242    }
243
244    #[test]
245    fn parameter_new() {
246        let param = Parameter::new("test");
247        assert_eq!(param.name, "test");
248        assert_eq!(param.param_type, ParameterType::String);
249        assert!(!param.required);
250        assert!(param.description.is_empty());
251        assert!(param.default.is_none());
252    }
253
254    #[test]
255    fn parameter_required_string() {
256        let param = Parameter::required_string("username");
257        assert_eq!(param.name, "username");
258        assert_eq!(param.param_type, ParameterType::String);
259        assert!(param.required);
260    }
261
262    #[test]
263    fn parameter_optional_string() {
264        let param = Parameter::optional_string("limit");
265        assert_eq!(param.name, "limit");
266        assert_eq!(param.param_type, ParameterType::String);
267        assert!(!param.required);
268    }
269
270    #[test]
271    fn parameter_builder() {
272        let param = Parameter::builder("count")
273            .param_type(ParameterType::Integer)
274            .required(true)
275            .description("Number of items")
276            .default(json!(10))
277            .build();
278
279        assert_eq!(param.name, "count");
280        assert_eq!(param.param_type, ParameterType::Integer);
281        assert!(param.required);
282        assert_eq!(param.description, "Number of items");
283        assert_eq!(param.default, Some(json!(10)));
284    }
285
286    #[test]
287    fn parameter_builder_with_enum() {
288        let param = Parameter::builder("format")
289            .param_type(ParameterType::String)
290            .enum_value(json!("json"))
291            .enum_value(json!("yaml"))
292            .enum_value(json!("toml"))
293            .build();
294
295        assert_eq!(param.enum_values.len(), 3);
296        assert!(param.enum_values.contains(&json!("json")));
297    }
298
299    #[test]
300    fn parameter_serialization() {
301        let param = Parameter::builder("path")
302            .param_type(ParameterType::String)
303            .required(true)
304            .description("File path")
305            .build();
306
307        let json = serde_json::to_string(&param).unwrap();
308        let parsed: Parameter = serde_json::from_str(&json).unwrap();
309
310        assert_eq!(param, parsed);
311    }
312
313    #[test]
314    fn parameter_deserialization_with_defaults() {
315        let json = r#"{"name": "test"}"#;
316        let param: Parameter = serde_json::from_str(json).unwrap();
317
318        assert_eq!(param.name, "test");
319        assert_eq!(param.param_type, ParameterType::String);
320        assert!(!param.required);
321    }
322
323    #[test]
324    fn parameter_type_matches_edge_cases() {
325        // Integer should match i64 and u64
326        let param_type = ParameterType::Integer;
327        assert!(param_type.matches(&json!(42)));
328        assert!(param_type.matches(&json!(-42)));
329        assert!(param_type.matches(&json!(9223372036854775807i64)));
330        assert!(!param_type.matches(&json!(3.14)));
331        assert!(!param_type.matches(&json!("42")));
332
333        // Number should match any numeric value
334        let param_type = ParameterType::Number;
335        assert!(param_type.matches(&json!(42)));
336        assert!(param_type.matches(&json!(3.14)));
337        assert!(param_type.matches(&json!(-42)));
338        assert!(!param_type.matches(&json!("42")));
339
340        // Boolean should only match true/false
341        let param_type = ParameterType::Boolean;
342        assert!(param_type.matches(&json!(true)));
343        assert!(param_type.matches(&json!(false)));
344        assert!(!param_type.matches(&json!(1)));
345        assert!(!param_type.matches(&json!("true")));
346
347        // Array should only match arrays
348        let param_type = ParameterType::Array;
349        assert!(param_type.matches(&json!([])));
350        assert!(param_type.matches(&json!([1, 2, 3])));
351        assert!(!param_type.matches(&json!({})));
352        assert!(!param_type.matches(&json!("[]")));
353
354        // Object should only match objects
355        let param_type = ParameterType::Object;
356        assert!(param_type.matches(&json!({})));
357        assert!(param_type.matches(&json!({"key": "value"})));
358        assert!(!param_type.matches(&json!([])));
359        assert!(!param_type.matches(&json!("{}")));
360    }
361
362    #[test]
363    fn parameter_enum_validation() {
364        let param = Parameter::builder("status")
365            .param_type(ParameterType::String)
366            .enum_value(json!("active"))
367            .enum_value(json!("inactive"))
368            .enum_value(json!("pending"))
369            .build();
370
371        assert_eq!(param.enum_values.len(), 3);
372        assert!(param.enum_values.contains(&json!("active")));
373        assert!(param.enum_values.contains(&json!("inactive")));
374        assert!(param.enum_values.contains(&json!("pending")));
375    }
376
377    #[test]
378    fn parameter_builder_complex() {
379        let param = Parameter::builder("data")
380            .param_type(ParameterType::Object)
381            .required(true)
382            .description("Complex data structure")
383            .default(json!({"nested": {"key": "value"}}))
384            .enum_value(json!({"type": "default"}))
385            .enum_value(json!({"type": "custom"}))
386            .build();
387
388        assert_eq!(param.name, "data");
389        assert_eq!(param.param_type, ParameterType::Object);
390        assert!(param.required);
391        assert_eq!(param.description, "Complex data structure");
392        assert_eq!(param.default, Some(json!({"nested": {"key": "value"}})));
393        assert_eq!(param.enum_values.len(), 2);
394    }
395
396    #[test]
397    fn parameter_serialization_roundtrip() {
398        let original = Parameter::builder("test_param")
399            .param_type(ParameterType::Integer)
400            .required(true)
401            .description("Test description")
402            .default(json!(42))
403            .build();
404
405        let serialized = serde_json::to_string(&original).unwrap();
406        let deserialized: Parameter = serde_json::from_str(&serialized).unwrap();
407
408        assert_eq!(original.name, deserialized.name);
409        assert_eq!(original.param_type, deserialized.param_type);
410        assert_eq!(original.required, deserialized.required);
411        assert_eq!(original.description, deserialized.description);
412        assert_eq!(original.default, deserialized.default);
413    }
414}