teamcraft/mcp/tools/
args.rs1use super::*;
4
5pub(super) fn bad(message: impl Into<String>) -> AppError {
6 AppError::BadRequest(message.into())
7}
8
9pub struct Args<'a>(Option<&'a Map<String, Value>>);
11
12impl<'a> Args<'a> {
13 pub fn new(value: &'a Value) -> Self {
14 Args(value.as_object())
15 }
16
17 pub(super) fn get(&self, key: &str) -> Option<&'a Value> {
18 self.0.and_then(|m| m.get(key)).filter(|v| !v.is_null())
19 }
20
21 pub fn opt_str(&self, key: &str) -> AppResult<Option<String>> {
23 match self.get(key) {
24 None => Ok(None),
25 Some(Value::String(s)) => {
26 let s = s.trim();
27 Ok((!s.is_empty()).then(|| s.to_string()))
28 }
29 Some(_) => Err(bad(format!("`{key}` must be a string."))),
30 }
31 }
32
33 pub fn req_str(&self, key: &str) -> AppResult<String> {
34 self.opt_str(key)?
35 .ok_or_else(|| bad(format!("`{key}` is required.")))
36 }
37
38 pub fn limit(&self, key: &str, default: i64, max: i64) -> AppResult<i64> {
40 let n = match self.get(key) {
41 None => default,
42 Some(Value::Number(n)) => n
43 .as_i64()
44 .or_else(|| n.as_f64().filter(|f| f.fract() == 0.0).map(|f| f as i64))
45 .ok_or_else(|| bad(format!("`{key}` must be an integer.")))?,
46 Some(Value::String(s)) => s
47 .trim()
48 .parse()
49 .map_err(|_| bad(format!("`{key}` must be an integer.")))?,
50 Some(_) => return Err(bad(format!("`{key}` must be an integer."))),
51 };
52 Ok(n.clamp(1, max))
53 }
54
55 pub fn count(&self, key: &str, default: i64, max: i64) -> AppResult<i64> {
57 let n = match self.get(key) {
58 None => default,
59 Some(Value::Number(n)) => n
60 .as_i64()
61 .ok_or_else(|| bad(format!("`{key}` must be an integer.")))?,
62 Some(Value::String(s)) => s
63 .trim()
64 .parse()
65 .map_err(|_| bad(format!("`{key}` must be an integer.")))?,
66 Some(_) => return Err(bad(format!("`{key}` must be an integer."))),
67 };
68 if !(0..=max).contains(&n) {
69 return Err(bad(format!("`{key}` must be between 0 and {max}.")));
70 }
71 Ok(n)
72 }
73
74 pub fn flag(&self, key: &str, default: bool) -> AppResult<bool> {
75 match self.get(key) {
76 None => Ok(default),
77 Some(Value::Bool(b)) => Ok(*b),
78 Some(Value::String(s)) => match s.trim().to_ascii_lowercase().as_str() {
79 "true" | "yes" | "1" => Ok(true),
80 "false" | "no" | "0" => Ok(false),
81 _ => Err(bad(format!("`{key}` must be true or false."))),
82 },
83 Some(_) => Err(bad(format!("`{key}` must be true or false."))),
84 }
85 }
86
87 pub fn list(&self, key: &str, max: usize) -> AppResult<Vec<String>> {
89 let out: Vec<String> = match self.get(key) {
90 None => Vec::new(),
91 Some(Value::String(s)) => vec![s.clone()],
92 Some(Value::Array(a)) => a
93 .iter()
94 .map(|v| {
95 v.as_str()
96 .map(str::to_string)
97 .ok_or_else(|| bad(format!("`{key}` must be a list of strings.")))
98 })
99 .collect::<AppResult<_>>()?,
100 Some(_) => return Err(bad(format!("`{key}` must be a list of strings."))),
101 };
102 let out: Vec<String> = out
103 .into_iter()
104 .map(|s| s.trim().to_string())
105 .filter(|s| !s.is_empty())
106 .collect();
107 if out.len() > max {
108 return Err(bad(format!("`{key}` takes at most {max} entries.")));
109 }
110 Ok(out)
111 }
112
113 pub(super) fn rule(&self) -> AppResult<Option<RevisionRule>> {
114 self.opt_str("rule")?
115 .map(|r| {
116 RevisionRule::parse(&r)
117 .ok_or_else(|| bad("rule must be latest, released or as_released."))
118 })
119 .transpose()
120 }
121}
122
123pub fn parse_item_ref(s: &str) -> (String, Option<String>) {
126 match s.trim().split_once('/') {
127 Some((number, code)) if !code.trim().is_empty() => {
128 (number.trim().to_string(), Some(code.trim().to_string()))
129 }
130 Some((number, _)) => (number.trim().to_string(), None),
131 None => (s.trim().to_string(), None),
132 }
133}