artifactize_tools/builtin/
input.rs1use crate::Builtin;
3use serde::{Deserialize, Deserializer, de};
4use serde_json::Value;
5
6#[derive(Debug)]
7pub enum Input {
8 Read(Read),
9 List(List),
10 Glob(Glob),
11 Grep(Grep),
12 ViewImage(ViewImage),
13}
14impl Input {
15 pub fn parse(builtin: Builtin, value: Value) -> Result<Self, String> {
16 let validator = crate::schema::compile(&super::input_schema(builtin))?;
17 crate::schema::validate(&validator, &value)?;
18 let input = match builtin {
19 Builtin::Graph | Builtin::Section | Builtin::Help | Builtin::Open => {
20 return Err("This builtin requires declared args.".into());
21 }
22 Builtin::Read => serde_json::from_value(value).map(Self::Read),
23 Builtin::List => serde_json::from_value(value).map(Self::List),
24 Builtin::Glob => serde_json::from_value(value).map(Self::Glob),
25 Builtin::Grep => serde_json::from_value(value).map(Self::Grep),
26 Builtin::ViewImage => serde_json::from_value(value).map(Self::ViewImage),
27 };
28 input.map_err(|_| "Tool arguments cannot be read as the declared builtin input.".into())
29 }
30}
31
32#[derive(Debug, Deserialize)]
33#[serde(deny_unknown_fields)]
34pub struct Read {
35 pub path: String,
36 #[serde(default = "first_line", deserialize_with = "integer")]
37 pub offset: usize,
38 #[serde(default = "read_lines", deserialize_with = "integer")]
39 pub limit: usize,
40}
41#[derive(Debug, Deserialize)]
42#[serde(deny_unknown_fields)]
43pub struct List {
44 #[serde(default)]
45 pub path: String,
46 #[serde(default, deserialize_with = "integer")]
47 pub offset: usize,
48 #[serde(default = "max_results", deserialize_with = "integer")]
49 pub limit: usize,
50}
51#[derive(Debug, Deserialize)]
52#[serde(deny_unknown_fields)]
53pub struct Glob {
54 pub pattern: String,
55 #[serde(default)]
56 pub path: String,
57}
58#[derive(Debug, Deserialize)]
59#[serde(rename_all = "camelCase", deny_unknown_fields)]
60pub struct Grep {
61 pub pattern: String,
62 #[serde(default)]
63 pub path: String,
64 #[serde(default)]
65 pub glob: Option<String>,
66 #[serde(default)]
67 pub case_insensitive: bool,
68 #[serde(default = "max_results", deserialize_with = "integer")]
69 pub max_results: usize,
70}
71#[derive(Debug, Deserialize)]
72#[serde(deny_unknown_fields)]
73pub struct ViewImage {
74 pub path: String,
75}
76
77fn first_line() -> usize {
78 1
79}
80fn read_lines() -> usize {
81 super::DEFAULT_READ_LINES
82}
83fn max_results() -> usize {
84 super::MAX_RESULTS
85}
86
87fn integer<'de, D: Deserializer<'de>>(deserializer: D) -> Result<usize, D::Error> {
90 struct Integer;
91 impl de::Visitor<'_> for Integer {
92 type Value = usize;
93 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94 formatter.write_str("a nonnegative safe JSON integer")
95 }
96 fn visit_u64<E: de::Error>(self, value: u64) -> Result<usize, E> {
97 self.visit_f64(value as f64)
98 }
99 fn visit_i64<E: de::Error>(self, value: i64) -> Result<usize, E> {
100 self.visit_f64(value as f64)
101 }
102 fn visit_f64<E: de::Error>(self, value: f64) -> Result<usize, E> {
103 if value.is_finite()
104 && value >= 0.0
105 && value.fract() == 0.0
106 && value <= crate::MAX_SAFE_JSON_INTEGER as f64
107 {
108 Ok(value as usize)
109 } else {
110 Err(E::custom("expected a nonnegative safe JSON integer"))
111 }
112 }
113 }
114 deserializer.deserialize_any(Integer)
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120 use serde_json::json;
121
122 #[test]
123 fn edge_rejects_out_of_range_and_unknown_arguments_without_caller_validation() {
124 for (builtin, value) in [
125 (Builtin::Read, json!({"path":"notes", "offset":0})),
126 (Builtin::Read, json!({"path":"notes", "limit":501})),
127 (Builtin::List, json!({"limit":201})),
128 (Builtin::Grep, json!({"pattern":"notes", "maxResults":0})),
129 (Builtin::Glob, json!({"pattern":""})),
130 (Builtin::ViewImage, json!({"path":"image", "unknown":true})),
131 ] {
132 assert!(Input::parse(builtin, value).is_err());
133 }
134 let parsed = Input::parse(
135 Builtin::Read,
136 json!({"path":"notes", "offset":1.0, "limit":2.0}),
137 )
138 .unwrap();
139 let Input::Read(input) = parsed else {
140 panic!("expected a read input")
141 };
142 assert_eq!((input.offset, input.limit), (1, 2));
143 }
144}