Skip to main content

routee_compass/plugin/input/
input_plugin_ops.rs

1use indoc::indoc;
2use serde_json::{json, Value};
3use std::rc::Rc;
4
5use super::InputPluginError;
6
7/// helper to return errors as JSON response objects which include the
8/// original request along with the error message
9pub fn package_error<E: ToString>(query: &mut Value, error: E) -> Value {
10    json!({
11        "request": query,
12        "error": error.to_string()
13    })
14}
15
16pub fn package_invariant_error(query: Option<&mut Value>, sub_section: Option<&Value>) -> Value {
17    let intro = indoc! {r#"
18    an input plugin has broken the invariant of the query state which requires
19    that the query's JSON representation has a top-level JSON Array ([]) whose only
20    elements are JSON objects ({}). please confirm that all included InputPlugin 
21    instances do not break this invariant.
22    "#
23    };
24
25    let json_msg = match query {
26        None => String::from(
27            "unable to display query state as it may have been modified during this process.",
28        ),
29        Some(ref q) => {
30            let json_intro = "here is the invalid query state that was found:";
31            let json = serde_json::to_string_pretty(q).unwrap_or_else(|e| {
32                format!("oops, i can't even serialize that query because of an error: {e}")
33            });
34            format!("{json_intro}\n\n{json}")
35        }
36    };
37
38    let msg = match sub_section {
39        None => format!("{intro}\n{json_msg}"),
40        Some(ss) => {
41            let ss_msg = "error triggered by the following sub-section:";
42            let ss_json = serde_json::to_string_pretty(&ss).unwrap_or_else(|e| {
43                format!("oops, i can't even serialize that sub-section because of an error: {e}")
44            });
45
46            format!("{intro}\n\n{json_msg}\n\n{ss_msg}\n\n{ss_json}")
47        }
48    };
49
50    match query {
51        Some(q) => package_error(q, msg),
52        None => package_error(&mut json![{"error": "unable to display query"}], msg),
53    }
54}
55
56pub type InputArrayOp<'a> = Rc<dyn Fn(&mut Value) -> Result<(), InputPluginError> + 'a>;
57
58/// executes an operation on an input query. maintains the invariant that
59/// input queries should always remain wrapped in a top-level JSON Array
60/// so that we can perform operations like grid search, which transform a
61/// single query into multiple child queries.
62pub fn json_array_op<'a>(query: &'a mut Value, op: InputArrayOp<'a>) -> Result<(), Value> {
63    match query {
64        Value::Array(queries) => {
65            for q in queries.iter_mut() {
66                op(q).map_err(|e| package_error(q, e))?;
67            }
68            json_array_flatten_in_place(query)
69        }
70        other => {
71            let error = package_invariant_error(None, Some(other));
72            Err(error)
73        }
74    }
75}
76
77/// flattens the result of input processing into a response vector, ensuring
78/// that the nesting and types are correct. the flatten operation effect occurs
79/// in-place on the function argument via a memory swap.
80pub fn json_array_flatten_in_place(result: &mut Value) -> Result<(), Value> {
81    if let Value::Array(top_array) = result {
82        if top_array.iter().all(|v| !v.is_array()) {
83            // short circuit if there are no nested arrays
84            return Ok(());
85        }
86
87        // de-nest sub-arrays into new vector
88        let mut flattened: Vec<&mut Value> = vec![];
89        for v1 in top_array.iter_mut() {
90            match v1 {
91                Value::Array(sub_array) => {
92                    for v2 in sub_array.iter_mut() {
93                        flattened.push(v2)
94                    }
95                }
96                other => flattened.push(other),
97            }
98        }
99        let mut flat_result = json![flattened];
100        std::mem::swap(result, &mut flat_result);
101        Ok(())
102    } else {
103        let error_response = package_invariant_error(Some(result), None);
104        Err(error_response)
105    }
106}
107
108/// flattens the result of input processing into a response vector, ensuring
109/// that the nesting and types are correct. returns a new vector of values,
110/// moving the valid input processing results into the new flattened vector.
111pub fn json_array_flatten(result: &mut Value) -> Result<Vec<Value>, Value> {
112    let mut flattened: Vec<Value> = vec![];
113    if !result.is_array() {
114        let error_response = package_invariant_error(Some(result), None);
115        return Err(error_response);
116    }
117    let mut error: Option<&mut Value> = None;
118    match result {
119        Value::Array(sub_array) => {
120            for sub_obj in sub_array.iter_mut() {
121                match sub_obj {
122                    Value::Object(obj) => {
123                        flattened.push(json![obj]);
124                    }
125                    other => {
126                        error = Some(other);
127                    }
128                }
129            }
130        }
131        other => {
132            error = Some(other);
133        }
134    }
135
136    match error {
137        Some(err) => {
138            let error_response = package_invariant_error(None, Some(err));
139            Err(error_response)?
140        }
141        None => Ok(flattened),
142    }
143}
144
145/// flattens the result of input processing in the case that the output of the
146/// input plugin is more than one JSON object. but if it is not a JSON array,
147/// then wrap it in a Vec.
148pub fn unpack_json_array_as_vec(result: &Value) -> Vec<Value> {
149    let mut error: Option<&Value> = None;
150    match result {
151        Value::Array(sub_array) => {
152            let mut flattened: Vec<Value> = vec![];
153            for sub_obj in sub_array.iter() {
154                match sub_obj {
155                    Value::Object(obj) => {
156                        flattened.push(json![obj]);
157                    }
158                    other => {
159                        error = Some(other);
160                    }
161                }
162            }
163            match error {
164                Some(_) => {
165                    let error_response = package_invariant_error(None, error);
166                    vec![error_response]
167                }
168                None => flattened,
169            }
170        }
171        _ => vec![result.clone()],
172    }
173}