evergreen/osrf/
method.rs

1use crate::osrf::app;
2use crate::osrf::message;
3use crate::osrf::session;
4use crate::EgResult;
5use crate::EgValue;
6use json::JsonValue;
7use std::fmt;
8
9pub type MethodHandler = fn(
10    &mut Box<dyn app::ApplicationWorker>,
11    &mut session::ServerSession,
12    message::MethodCall,
13) -> EgResult<()>;
14
15#[derive(Debug, Copy, Clone, PartialEq)]
16pub enum ParamCount {
17    Any,
18    Zero,
19    Exactly(u8),
20    AtLeast(u8),
21    Range(u8, u8), // Inclusive
22}
23
24impl ParamCount {
25    /// Returns true if the number of params provided matches the
26    /// number specified by the ParamCount enum.
27    ///
28    /// ```
29    /// use evergreen::osrf::method::ParamCount;
30    /// assert!(ParamCount::matches(&ParamCount::Any, 0));
31    /// assert!(!ParamCount::matches(&ParamCount::Exactly(1), 10));
32    /// assert!(ParamCount::matches(&ParamCount::AtLeast(10), 20));
33    /// assert!(!ParamCount::matches(&ParamCount::AtLeast(20), 10));
34    /// assert!(ParamCount::matches(&ParamCount::Range(4, 6), 5));
35    /// ```
36    pub fn matches(pc: &ParamCount, count: u8) -> bool {
37        match *pc {
38            ParamCount::Any => true,
39            ParamCount::Zero => count == 0,
40            ParamCount::Exactly(c) => count == c,
41            ParamCount::AtLeast(c) => count >= c,
42            ParamCount::Range(s, e) => s <= count && e >= count,
43        }
44    }
45
46    /// Minimum number of parameters required to satisfy this
47    /// ParamCount definition.
48    pub fn minimum(&self) -> u8 {
49        match *self {
50            ParamCount::Any => 0,
51            ParamCount::Zero => 0,
52            ParamCount::Exactly(c) => c,
53            ParamCount::AtLeast(c) => c,
54            ParamCount::Range(s, _) => s,
55        }
56    }
57}
58
59impl fmt::Display for ParamCount {
60    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
61        match *self {
62            ParamCount::Any => write!(f, "Any"),
63            ParamCount::Zero => write!(f, "Zero"),
64            ParamCount::Exactly(c) => write!(f, "Exactly {}", c),
65            ParamCount::AtLeast(c) => write!(f, "AtLeast {}", c),
66            ParamCount::Range(s, e) => write!(f, "Range {}..{}", s, e),
67        }
68    }
69}
70
71/// Simplest possible breakdown of supported parameter base types.
72#[derive(Clone, Copy, Debug)]
73pub enum ParamDataType {
74    String,
75    Number,
76    Array,
77    Object, // JsonValue::Object or other object-y thing
78    Boolish,
79    Scalar, // Not an Object or Array.
80    Any,
81}
82
83impl fmt::Display for ParamDataType {
84    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
85        let s = match *self {
86            ParamDataType::String => "String",
87            ParamDataType::Number => "Number",
88            ParamDataType::Array => "Array",
89            ParamDataType::Object => "Object",
90            ParamDataType::Boolish => "Boolish",
91            ParamDataType::Scalar => "Scalar",
92            ParamDataType::Any => "Any",
93        };
94        write!(f, "{s}")
95    }
96}
97
98impl ParamDataType {
99    /// True if the provided parameter value matches our type.
100    ///
101    /// This is a superficial inspection of the parameter type.  E.g.,
102    /// we don't care about the contents of an array.
103    pub fn matches(&self, param: &EgValue) -> bool {
104        match *self {
105            ParamDataType::String => param.is_string(),
106            ParamDataType::Number => param.is_number(),
107            ParamDataType::Array => param.is_array(),
108            ParamDataType::Object => param.is_object(),
109            ParamDataType::Boolish => {
110                param.is_boolean() || param.is_number() || param.is_string() || param.is_null()
111            }
112            ParamDataType::Scalar => {
113                param.is_boolean() || param.is_number() || param.is_string() || param.is_null()
114            }
115            ParamDataType::Any => true,
116        }
117    }
118}
119
120#[derive(Clone, Debug)]
121pub struct StaticParam {
122    pub name: &'static str,
123    pub datatype: ParamDataType,
124    pub desc: &'static str,
125}
126
127#[derive(Clone, Debug)]
128pub struct Param {
129    pub name: String,
130    pub datatype: ParamDataType,
131    pub desc: Option<String>,
132}
133
134impl Param {
135    pub fn to_eg_value(&self) -> EgValue {
136        EgValue::from_json_value_plain(json::object! {
137            "name": self.name.as_str(),
138            "datatype": self.datatype.to_string(),
139            "desc": match self.desc.as_ref() {
140                Some(d) => d.as_str().into(),
141                _ => JsonValue::Null,
142            }
143        })
144    }
145}
146
147/// A variation of a Method that can be used when creating static
148/// method definitions.
149pub struct StaticMethodDef {
150    pub name: &'static str,
151    pub desc: &'static str,
152    pub param_count: ParamCount,
153    pub handler: MethodHandler,
154    pub params: &'static [StaticParam],
155}
156
157impl StaticMethodDef {
158    pub fn name(&self) -> &str {
159        self.name
160    }
161    pub fn param_count(&self) -> &ParamCount {
162        &self.param_count
163    }
164    pub fn handler(&self) -> &MethodHandler {
165        &self.handler
166    }
167
168    /// Translate static method content into proper Method's
169    pub fn into_method(&self, api_prefix: &str) -> MethodDef {
170        let mut params: Vec<Param> = Vec::new();
171
172        for p in self.params {
173            let mut param = Param {
174                name: p.name.to_string(),
175                datatype: p.datatype,
176                desc: None,
177            };
178
179            if !p.desc.is_empty() {
180                param.desc = Some(p.desc.to_string());
181            }
182
183            params.push(param)
184        }
185
186        let mut m = MethodDef::new(
187            &format!("{}.{}", api_prefix, self.name()),
188            *self.param_count(),
189            self.handler,
190        );
191
192        if !params.is_empty() {
193            m.params = Some(params);
194        }
195
196        if !self.desc.is_empty() {
197            m.desc = Some(self.desc.to_string());
198        }
199
200        m
201    }
202}
203
204#[derive(Clone)]
205pub struct MethodDef {
206    pub name: String,
207    pub desc: Option<String>,
208    pub param_count: ParamCount,
209    pub handler: MethodHandler,
210    pub params: Option<Vec<Param>>,
211}
212
213impl MethodDef {
214    pub fn new(name: &str, param_count: ParamCount, handler: MethodHandler) -> MethodDef {
215        MethodDef {
216            handler,
217            param_count,
218            params: None,
219            desc: None,
220            name: name.to_string(),
221        }
222    }
223
224    pub fn param_count(&self) -> &ParamCount {
225        &self.param_count
226    }
227
228    pub fn handler(&self) -> MethodHandler {
229        self.handler
230    }
231
232    pub fn name(&self) -> &str {
233        &self.name
234    }
235
236    pub fn set_name(&mut self, name: &str) {
237        self.name = name.to_string();
238    }
239
240    pub fn params(&self) -> Option<&Vec<Param>> {
241        self.params.as_ref()
242    }
243
244    pub fn desc(&self) -> Option<&str> {
245        self.desc.as_deref()
246    }
247    pub fn set_desc(&mut self, desc: &str) {
248        self.desc = Some(desc.to_string());
249    }
250    pub fn add_param(&mut self, param: Param) {
251        let params = match self.params.as_mut() {
252            Some(p) => p,
253            None => {
254                self.params = Some(Vec::new());
255                self.params.as_mut().unwrap()
256            }
257        };
258
259        params.push(param);
260    }
261
262    pub fn to_eg_value(&self) -> EgValue {
263        let mut pa = EgValue::new_array();
264        if let Some(params) = self.params() {
265            for param in params {
266                pa.push(param.to_eg_value()).expect("Is Array");
267            }
268        }
269
270        EgValue::from_json_value_plain(json::object! {
271            "api_name": self.name(),
272            "argc": self.param_count().to_string(),
273            "params": pa.into_json_value(),
274            // All Rust methods are streaming.
275            "stream": JsonValue::Boolean(true),
276            "desc": match self.desc() {
277                Some(d) => d.into(),
278                _ => JsonValue::Null,
279            }
280        })
281    }
282
283    /// Produces e.g. "foo.bar.baz('param1', 'param2')"
284    pub fn to_summary_string(&self) -> String {
285        let mut s = self.name().to_string();
286
287        match self.param_count {
288            ParamCount::Zero => {}
289            _ => s += " (",
290        }
291
292        if let Some(params) = self.params() {
293            let minimum = self.param_count.minimum();
294            for (idx, param) in params.iter().enumerate() {
295                let required = if idx <= minimum as usize {
296                    "*" // required
297                } else {
298                    ""
299                };
300
301                s += &format!("{required}'{}',", param.name);
302            }
303            s.pop(); // remove trailing ","
304        } else if self.param_count == ParamCount::Any {
305            s += "..";
306        }
307
308        if let ParamCount::AtLeast(_) = self.param_count {
309            s += ",..";
310        }
311
312        match self.param_count {
313            ParamCount::Zero => {}
314            _ => s += ")",
315        }
316
317        s
318    }
319}