artifactize_tools/builtin/
input.rs1use crate::Builtin;
3use globset::GlobMatcher;
4use regex::{Regex, RegexBuilder};
5use serde::{Deserialize, Deserializer, de};
6use serde_json::Value;
7
8#[derive(Debug)]
9pub enum Input {
10 Read(Read),
11 List(List),
12 Glob(Glob),
13 Grep(Grep),
14 ViewImage(ViewImage),
15}
16impl Input {
17 pub(crate) fn glob(pattern: &str, path: String) -> Result<Self, String> {
19 pattern_size(pattern)?;
20 Ok(Self::Glob(Glob {
21 pattern: super::glob(pattern)?,
22 path: LogicalPath::parse(path)?,
23 }))
24 }
25
26 pub(crate) fn grep(
27 pattern: &str,
28 path: String,
29 glob: Option<&str>,
30 insensitive: bool,
31 ) -> Result<Self, String> {
32 Grep::new(
33 pattern,
34 LogicalPath::parse(path)?,
35 glob,
36 insensitive,
37 super::MAX_RESULTS,
38 )
39 .map(Self::Grep)
40 }
41
42 pub fn parse(builtin: Builtin, value: Value) -> Result<Self, String> {
43 let validator = crate::schema::compile(&super::input_schema(builtin))?;
44 crate::schema::validate(&validator, &value)?;
45 let input = match builtin {
46 Builtin::Graph | Builtin::Section | Builtin::Help | Builtin::Open => {
47 return Err("This builtin requires declared args.".into());
48 }
49 Builtin::Read => serde_json::from_value(value).map(Self::Read),
50 Builtin::List => serde_json::from_value(value).map(Self::List),
51 Builtin::Glob => serde_json::from_value(value).map(Self::Glob),
52 Builtin::Grep => serde_json::from_value(value).map(Self::Grep),
53 Builtin::ViewImage => serde_json::from_value(value).map(Self::ViewImage),
54 };
55 input.map_err(|error| error.to_string())
56 }
57}
58
59#[derive(Debug, Deserialize)]
60#[serde(deny_unknown_fields)]
61pub struct Read {
62 pub path: LogicalPath,
63 #[serde(default = "first_line", deserialize_with = "integer")]
64 pub offset: usize,
65 #[serde(default = "read_lines", deserialize_with = "integer")]
66 pub limit: usize,
67}
68#[derive(Debug, Deserialize)]
69#[serde(deny_unknown_fields)]
70pub struct List {
71 #[serde(default)]
72 pub path: LogicalPath,
73 #[serde(default, deserialize_with = "integer")]
74 pub offset: usize,
75 #[serde(default = "max_results", deserialize_with = "integer")]
76 pub limit: usize,
77}
78#[derive(Debug, Deserialize)]
79#[serde(deny_unknown_fields)]
80pub struct Glob {
81 #[serde(deserialize_with = "glob_pattern")]
82 pub pattern: GlobMatcher,
83 #[serde(default)]
84 pub path: LogicalPath,
85}
86#[derive(Debug)]
87pub struct Grep {
88 pub pattern: Regex,
89 pub path: LogicalPath,
90 pub glob: Option<GlobMatcher>,
91 pub max_results: usize,
92}
93impl<'de> Deserialize<'de> for Grep {
94 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
95 #[derive(Deserialize)]
96 #[serde(rename_all = "camelCase", deny_unknown_fields)]
97 struct Arguments {
98 pattern: String,
99 #[serde(default)]
100 path: LogicalPath,
101 #[serde(default)]
102 glob: Option<String>,
103 #[serde(default)]
104 case_insensitive: bool,
105 #[serde(default = "max_results", deserialize_with = "integer")]
106 max_results: usize,
107 }
108 let args = Arguments::deserialize(deserializer)?;
109 Self::new(
110 &args.pattern,
111 args.path,
112 args.glob.as_deref(),
113 args.case_insensitive,
114 args.max_results,
115 )
116 .map_err(de::Error::custom)
117 }
118}
119
120impl Grep {
121 fn new(
122 pattern: &str,
123 path: LogicalPath,
124 glob: Option<&str>,
125 insensitive: bool,
126 max_results: usize,
127 ) -> Result<Self, String> {
128 pattern_size(pattern)?;
129 if let Some(glob) = glob {
130 pattern_size(glob)?;
131 }
132 let pattern = RegexBuilder::new(pattern)
133 .case_insensitive(insensitive)
134 .size_limit(super::REGEX_BYTES)
135 .build()
136 .map_err(|_| "Invalid or oversized regex pattern.".to_owned())?;
137 Ok(Self {
138 pattern,
139 path,
140 glob: glob.map(super::glob).transpose()?,
141 max_results,
142 })
143 }
144}
145
146fn pattern_size(pattern: &str) -> Result<(), String> {
147 if pattern.is_empty() || pattern.chars().count() > super::MAX_PATTERN_UNITS {
148 Err("Pattern must be nonempty and fit the declared pattern limit.".into())
149 } else {
150 Ok(())
151 }
152}
153
154#[derive(Debug, Default)]
157pub struct LogicalPath(String);
158impl LogicalPath {
159 pub fn parse(path: String) -> Result<Self, String> {
160 if path != "." {
161 crate::scope::logical_path(&path).map_err(|error| error.to_string())?;
162 }
163 Ok(Self(path))
164 }
165 pub fn as_str(&self) -> &str {
166 &self.0
167 }
168}
169impl std::ops::Deref for LogicalPath {
170 type Target = str;
171 fn deref(&self) -> &str {
172 self.as_str()
173 }
174}
175impl<'de> Deserialize<'de> for LogicalPath {
176 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
177 let path = String::deserialize(deserializer)?;
178 Self::parse(path).map_err(de::Error::custom)
179 }
180}
181
182fn glob_pattern<'de, D: Deserializer<'de>>(deserializer: D) -> Result<GlobMatcher, D::Error> {
183 super::glob(&String::deserialize(deserializer)?).map_err(de::Error::custom)
184}
185
186#[derive(Debug, Deserialize)]
187#[serde(deny_unknown_fields)]
188pub struct ViewImage {
189 pub path: LogicalPath,
190}
191
192fn first_line() -> usize {
193 1
194}
195fn read_lines() -> usize {
196 super::DEFAULT_READ_LINES
197}
198fn max_results() -> usize {
199 super::MAX_RESULTS
200}
201
202fn integer<'de, D: Deserializer<'de>>(deserializer: D) -> Result<usize, D::Error> {
205 struct Integer;
206 impl de::Visitor<'_> for Integer {
207 type Value = usize;
208 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
209 formatter.write_str("a nonnegative safe JSON integer")
210 }
211 fn visit_u64<E: de::Error>(self, value: u64) -> Result<usize, E> {
212 self.visit_f64(value as f64)
213 }
214 fn visit_i64<E: de::Error>(self, value: i64) -> Result<usize, E> {
215 self.visit_f64(value as f64)
216 }
217 fn visit_f64<E: de::Error>(self, value: f64) -> Result<usize, E> {
218 if value.is_finite()
219 && value >= 0.0
220 && value.fract() == 0.0
221 && value <= crate::MAX_SAFE_JSON_INTEGER as f64
222 {
223 Ok(value as usize)
224 } else {
225 Err(E::custom("expected a nonnegative safe JSON integer"))
226 }
227 }
228 }
229 deserializer.deserialize_any(Integer)
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235 use serde_json::json;
236
237 #[test]
238 fn edge_rejects_out_of_range_and_unknown_arguments_without_caller_validation() {
239 for (builtin, value) in [
240 (Builtin::Read, json!({"path":"notes", "offset":0})),
241 (Builtin::Read, json!({"path":"notes", "limit":501})),
242 (Builtin::List, json!({"limit":201})),
243 (Builtin::Grep, json!({"pattern":"notes", "maxResults":0})),
244 (Builtin::Glob, json!({"pattern":""})),
245 (Builtin::Read, json!({"path":"../notes"})),
246 (Builtin::List, json!({"path":"nested//notes"})),
247 (Builtin::Glob, json!({"pattern":"../*"})),
248 (Builtin::Grep, json!({"pattern":"["})),
249 (Builtin::Grep, json!({"pattern":"notes", "glob":"../*"})),
250 (Builtin::ViewImage, json!({"path":"image", "unknown":true})),
251 ] {
252 assert!(Input::parse(builtin, value).is_err());
253 }
254 let parsed = Input::parse(
255 Builtin::Read,
256 json!({"path":"notes", "offset":1.0, "limit":2.0}),
257 )
258 .unwrap();
259 let Input::Read(input) = parsed else {
260 panic!("expected a read input")
261 };
262 assert_eq!((input.offset, input.limit), (1, 2));
263 }
264}