Skip to main content

limit_cli/tools/browser/
args.rs

1//! Argument extraction extension trait for JSON values
2//!
3//! Provides convenient methods for extracting typed arguments from `serde_json::Value`
4//! with standardized error formatting.
5
6use limit_agent::error::AgentError;
7use serde_json::Value;
8
9/// Extension trait for extracting arguments from JSON values
10///
11/// This trait provides methods for extracting typed values from `serde_json::Value`
12/// with standardized error messages for missing arguments.
13///
14/// # Example
15///
16/// ```ignore
17/// use serde_json::json;
18/// use limit_cli::tools::browser::args::ArgsExt;
19///
20/// let args = json!({
21///     "url": "https://example.com",
22///     "timeout": 5000,
23///     "optional": "value"
24/// });
25///
26/// // Required arguments
27/// let url = args.get_str("url", "open")?;
28/// let timeout = args.get_u64("timeout", "configure")?;
29///
30/// // Optional arguments
31/// let optional = args.get_opt_str("optional");
32/// ```
33pub trait ArgsExt {
34    /// Get a required string argument
35    ///
36    /// # Arguments
37    ///
38    /// * `key` - The key to look up in the JSON object
39    /// * `action` - The action name for error reporting
40    ///
41    /// # Returns
42    ///
43    /// * `Ok(&str)` - The string value if found
44    /// * `Err(AgentError)` - If the key is missing or not a string
45    ///
46    /// # Example
47    ///
48    /// ```ignore
49    /// let url = args.get_str("url", "open")?;
50    /// ```
51    fn get_str(&self, key: &str, action: &str) -> Result<&str, AgentError>;
52
53    /// Get an optional string argument
54    ///
55    /// # Arguments
56    ///
57    /// * `key` - The key to look up in the JSON object
58    ///
59    /// # Returns
60    ///
61    /// * `Some(&str)` - The string value if found
62    /// * `None` - If the key is missing or not a string
63    ///
64    /// # Example
65    ///
66    /// ```ignore
67    /// let path = args.get_opt_str("path");
68    /// ```
69    fn get_opt_str(&self, key: &str) -> Option<&str>;
70
71    /// Get a required u64 argument
72    ///
73    /// # Arguments
74    ///
75    /// * `key` - The key to look up in the JSON object
76    /// * `action` - The action name for error reporting
77    ///
78    /// # Returns
79    ///
80    /// * `Ok(u64)` - The u64 value if found
81    /// * `Err(AgentError)` - If the key is missing or not a u64
82    ///
83    /// # Example
84    ///
85    /// ```ignore
86    /// let index = args.get_u64("index", "tab_select")?;
87    /// ```
88    fn get_u64(&self, key: &str, action: &str) -> Result<u64, AgentError>;
89
90    /// Get an optional u64 argument
91    ///
92    /// # Arguments
93    ///
94    /// * `key` - The key to look up in the JSON object
95    ///
96    /// # Returns
97    ///
98    /// * `Some(u64)` - The u64 value if found
99    /// * `None` - If the key is missing or not a u64
100    ///
101    /// # Example
102    ///
103    /// ```ignore
104    /// let pixels = args.get_opt_u64("pixels");
105    /// ```
106    fn get_opt_u64(&self, key: &str) -> Option<u64>;
107
108    /// Get a required f64 argument
109    ///
110    /// # Arguments
111    ///
112    /// * `key` - The key to look up in the JSON object
113    /// * `action` - The action name for error reporting
114    ///
115    /// # Returns
116    ///
117    /// * `Ok(f64)` - The f64 value if found
118    /// * `Err(AgentError)` - If the key is missing or not an f64
119    ///
120    /// # Example
121    ///
122    /// ```ignore
123    /// let latitude = args.get_f64("latitude", "set_geo")?;
124    /// ```
125    fn get_f64(&self, key: &str, action: &str) -> Result<f64, AgentError>;
126
127    /// Get an optional f64 argument
128    ///
129    /// # Arguments
130    ///
131    /// * `key` - The key to look up in the JSON object
132    ///
133    /// # Returns
134    ///
135    /// * `Some(f64)` - The f64 value if found
136    /// * `None` - If the key is missing or not an f64
137    ///
138    /// # Example
139    ///
140    /// ```ignore
141    /// let scale = args.get_opt_f64("scale");
142    /// ```
143    fn get_opt_f64(&self, key: &str) -> Option<f64>;
144}
145
146impl ArgsExt for Value {
147    fn get_str(&self, key: &str, action: &str) -> Result<&str, AgentError> {
148        self.get(key).and_then(|v| v.as_str()).ok_or_else(|| {
149            AgentError::ToolError(format!("Missing '{}' argument for {} action", key, action))
150        })
151    }
152
153    fn get_opt_str(&self, key: &str) -> Option<&str> {
154        self.get(key).and_then(|v| v.as_str())
155    }
156
157    fn get_u64(&self, key: &str, action: &str) -> Result<u64, AgentError> {
158        self.get(key).and_then(|v| v.as_u64()).ok_or_else(|| {
159            AgentError::ToolError(format!("Missing '{}' argument for {} action", key, action))
160        })
161    }
162
163    fn get_opt_u64(&self, key: &str) -> Option<u64> {
164        self.get(key).and_then(|v| v.as_u64())
165    }
166
167    fn get_f64(&self, key: &str, action: &str) -> Result<f64, AgentError> {
168        self.get(key).and_then(|v| v.as_f64()).ok_or_else(|| {
169            AgentError::ToolError(format!("Missing '{}' argument for {} action", key, action))
170        })
171    }
172
173    fn get_opt_f64(&self, key: &str) -> Option<f64> {
174        self.get(key).and_then(|v| v.as_f64())
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181
182    #[test]
183    fn test_get_str_success() {
184        let args = serde_json::json!({"url": "https://example.com"});
185        let result = args.get_str("url", "open");
186        assert!(result.is_ok());
187        assert_eq!(result.unwrap(), "https://example.com");
188    }
189
190    #[test]
191    fn test_get_str_missing_key() {
192        let args = serde_json::json!({});
193        let result = args.get_str("url", "open");
194        assert!(result.is_err());
195        let err = result.unwrap_err();
196        assert!(err
197            .to_string()
198            .contains("Missing 'url' argument for open action"));
199    }
200
201    #[test]
202    fn test_get_str_wrong_type() {
203        let args = serde_json::json!({"url": 123});
204        let result = args.get_str("url", "open");
205        assert!(result.is_err());
206        let err = result.unwrap_err();
207        assert!(err
208            .to_string()
209            .contains("Missing 'url' argument for open action"));
210    }
211
212    #[test]
213    fn test_get_opt_str_some() {
214        let args = serde_json::json!({"path": "/tmp/screenshot.png"});
215        let result = args.get_opt_str("path");
216        assert_eq!(result, Some("/tmp/screenshot.png"));
217    }
218
219    #[test]
220    fn test_get_opt_str_none() {
221        let args = serde_json::json!({});
222        let result = args.get_opt_str("path");
223        assert_eq!(result, None);
224    }
225
226    #[test]
227    fn test_get_opt_str_wrong_type() {
228        let args = serde_json::json!({"path": 123});
229        let result = args.get_opt_str("path");
230        assert_eq!(result, None);
231    }
232
233    #[test]
234    fn test_get_u64_success() {
235        let args = serde_json::json!({"index": 42});
236        let result = args.get_u64("index", "tab_select");
237        assert!(result.is_ok());
238        assert_eq!(result.unwrap(), 42);
239    }
240
241    #[test]
242    fn test_get_u64_missing_key() {
243        let args = serde_json::json!({});
244        let result = args.get_u64("index", "tab_select");
245        assert!(result.is_err());
246        let err = result.unwrap_err();
247        assert!(err
248            .to_string()
249            .contains("Missing 'index' argument for tab_select action"));
250    }
251
252    #[test]
253    fn test_get_u64_wrong_type() {
254        let args = serde_json::json!({"index": "not a number"});
255        let result = args.get_u64("index", "tab_select");
256        assert!(result.is_err());
257    }
258
259    #[test]
260    fn test_get_opt_u64_some() {
261        let args = serde_json::json!({"pixels": 1000});
262        let result = args.get_opt_u64("pixels");
263        assert_eq!(result, Some(1000));
264    }
265
266    #[test]
267    fn test_get_opt_u64_none() {
268        let args = serde_json::json!({});
269        let result = args.get_opt_u64("pixels");
270        assert_eq!(result, None);
271    }
272
273    #[test]
274    fn test_get_f64_success() {
275        let args = serde_json::json!({"latitude": 40.7128});
276        let result = args.get_f64("latitude", "set_geo");
277        assert!(result.is_ok());
278        assert_eq!(result.unwrap(), 40.7128);
279    }
280
281    #[test]
282    fn test_get_f64_missing_key() {
283        let args = serde_json::json!({});
284        let result = args.get_f64("latitude", "set_geo");
285        assert!(result.is_err());
286        let err = result.unwrap_err();
287        assert!(err
288            .to_string()
289            .contains("Missing 'latitude' argument for set_geo action"));
290    }
291
292    #[test]
293    fn test_get_f64_wrong_type() {
294        let args = serde_json::json!({"latitude": "not a number"});
295        let result = args.get_f64("latitude", "set_geo");
296        assert!(result.is_err());
297    }
298
299    #[test]
300    fn test_get_opt_f64_some() {
301        let args = serde_json::json!({"scale": 1.5});
302        let result = args.get_opt_f64("scale");
303        assert_eq!(result, Some(1.5));
304    }
305
306    #[test]
307    fn test_get_opt_f64_none() {
308        let args = serde_json::json!({});
309        let result = args.get_opt_f64("scale");
310        assert_eq!(result, None);
311    }
312
313    #[test]
314    fn test_get_u64_with_float() {
315        // serde_json can parse floats as f64, but as_u64() should work for integers
316        let args = serde_json::json!({"index": 42u64});
317        let result = args.get_u64("index", "tab_select");
318        assert!(result.is_ok());
319        assert_eq!(result.unwrap(), 42);
320    }
321
322    #[test]
323    fn test_get_f64_with_integer() {
324        // f64 can be parsed from integers
325        let args = serde_json::json!({"scale": 2});
326        let result = args.get_f64("scale", "set_viewport");
327        assert!(result.is_ok());
328        assert_eq!(result.unwrap(), 2.0);
329    }
330}