routee_compass/plugin/input/default/inject/
inject_plugin.rs1use super::{CoordinateOrientation, WriteMode};
2use crate::{
3 app::search::SearchApp,
4 plugin::input::{input_plugin::InputPlugin, InputJsonExtensions, InputPluginError},
5};
6use routee_compass_core::util::geo::PolygonalRTree;
7use serde_json::Value;
8use std::sync::Arc;
9
10pub enum InjectInputPlugin {
11 Basic {
12 key: String,
13 value: serde_json::Value,
14 write_mode: WriteMode,
15 },
16 Spatial {
17 key: String,
18 values: PolygonalRTree<f32, Value>,
19 write_mode: WriteMode,
20 orientation: CoordinateOrientation,
21 default: Option<Value>,
22 },
23}
24
25impl InputPlugin for InjectInputPlugin {
26 fn process(
27 &self,
28 input: &mut serde_json::Value,
29 _search_app: Arc<SearchApp>,
30 ) -> Result<(), InputPluginError> {
31 process_inject(self, input)
32 }
33}
34
35pub fn process_inject(
36 plugin: &InjectInputPlugin,
37 input: &mut serde_json::Value,
38) -> Result<(), InputPluginError> {
39 match plugin {
40 InjectInputPlugin::Basic {
41 key,
42 value,
43 write_mode,
44 } => write_mode.write_to_query(input, key, value),
45 InjectInputPlugin::Spatial {
46 values,
47 key,
48 write_mode,
49 orientation,
50 default,
51 } => {
52 let coord = match orientation {
53 CoordinateOrientation::Origin => input.get_origin_coordinate(),
54 CoordinateOrientation::Destination => match input.get_destination_coordinate() {
55 Ok(Some(coord)) => Ok(coord),
56 Ok(None) => Err(InputPluginError::InputPluginFailed(String::from(
57 "destination-oriented spatial inject plugin but query has no destination",
58 ))),
59 Err(e) => Err(e),
60 },
61 }?;
62 let point = geo::Geometry::Point(geo::Point(coord));
63 let mut intersect_iter = values.intersection(&point).map_err(|e| {
64 InputPluginError::InputPluginFailed(format!(
65 "failure while intersecting spatial inject data: {e}"
66 ))
67 })?;
68 match (intersect_iter.next(), default) {
69 (None, None) => {
70 Ok(())
72 }
73 (None, Some(default_value)) => {
74 write_mode.write_to_query(input, key, default_value)
76 }
77 (Some(found), _) => {
78 write_mode.write_to_query(input, key, &found.data)
80 }
81 }
82 }
83 }
84}
85
86#[cfg(test)]
87mod test {
88 use super::{process_inject, InjectInputPlugin};
89 use crate::plugin::input::default::inject::{
90 inject_plugin_config::SpatialInjectPlugin, CoordinateOrientation, InjectPluginConfig,
91 WriteMode,
92 };
93 use config::Config;
94 use itertools::Itertools;
95 use serde_json::{json, Value};
96 use std::path::Path;
97
98 #[test]
99 fn test_kv() {
100 let mut query = json!({});
101 let key = String::from("key_on_query");
102 let value = json![{"k": "v"}];
103 let plugin = InjectInputPlugin::Basic {
104 key: key.clone(),
105 value: value.clone(),
106 write_mode: WriteMode::Overwrite,
107 };
108 process_inject(&plugin, &mut query).expect("test failed");
109 let result_value = query.get(&key).expect("test failed: key was not set");
110 assert_eq!(
111 result_value, &value,
112 "test failed: value stored in GeoJSON with matching location does not match"
113 )
114 }
115
116 #[test]
117 fn test_kv_from_file() {
118 let plugins = test_kv_conf();
119 let result = plugins.iter().fold(json![{}], |mut input, plugin| {
120 process_inject(plugin, &mut input).unwrap();
121 input
122 });
123 let result_string = serde_json::to_string(&result).unwrap();
124 let expected = String::from(
125 r#"{"test_a":{"foo":"bar","baz":"bees"},"test_b":["test",5,3.14159],"test_c":[0,0,0,0]}"#,
126 );
127 assert_eq!(result_string, expected);
128 }
129 #[test]
130 fn test_spatial_contains() {
131 let mut query = json!({
132 "origin_x": -105.11011135094863,
133 "origin_y": 39.83906153425838
134 });
135 let source_key = Some(String::from("key_on_geojson"));
136 let key = String::from("key_on_query");
137 let plugin = setup_spatial(&source_key, &key, &None);
138 process_inject(&plugin, &mut query).expect("test failed");
139 let value = query.get(&key).expect("test failed: key was not set");
140 let value_number = value.as_i64().expect("test failed: value was not a number");
141 assert_eq!(
142 value_number, 5000,
143 "test failed: value stored in GeoJSON with matching location was not injected"
144 )
145 }
146
147 #[test]
148 fn test_spatial_not_contains() {
149 let mut query = json!({
150 "origin_x": -105.07021837975549,
151 "origin_y": 39.93602243844981
152 });
153 let source_key = Some(String::from("key_on_geojson"));
154 let key = String::from("key_on_query");
155 let default = String::from("found default");
156 let plugin = setup_spatial(&source_key, &key, &Some(json![default.clone()]));
157 process_inject(&plugin, &mut query).expect("test failed");
158 let value = query.get(&key).expect("test failed: key was not set");
159 let value_str = value.as_str().expect("test failed: value was not a str");
160 assert_eq!(
161 value_str, &default,
162 "test failed: value stored in GeoJSON with location that does not match did not return default fill value"
163 )
164 }
165
166 #[test]
167 fn test_spatial_not_contains_no_default() {
168 let mut query = json!({
169 "origin_x": -105.07021837975549,
170 "origin_y": 39.93602243844981
171 });
172 let expected = query.clone();
173 let source_key = Some(String::from("key_on_geojson"));
174 let key = String::from("key_on_query");
175 let plugin = setup_spatial(&source_key, &key, &None);
176 process_inject(&plugin, &mut query).expect("test failed");
177 assert_eq!(
178 query, expected,
179 "test failed: process should be idempotent when the query is not contained and there is no default value"
180 )
181 }
182
183 #[test]
184 fn test_spatial_from_json() {
185 let source_key = String::from("key_on_geojson");
186 let key = String::from("key_on_query");
187 let filepath = if cfg!(target_os = "windows") {
188 test_geojson_filepath().replace("\\", "\\\\")
190 } else {
191 test_geojson_filepath()
193 };
194 let conf_str = format!(
195 r#"
196 {{
197 "type": "inject",
198 "format": "spatial_key_value",
199 "spatial_input_file": "{}",
200 "source_key": "{}",
201 "key": "{}",
202 "write_mode": "overwrite",
203 "orientation": "origin"
204 }}
205 "#,
206 filepath, &source_key, &key
207 );
208 let conf: InjectPluginConfig =
209 serde_json::from_str(&conf_str).expect("failed to decode configuration");
210 let plugin = conf.build().expect("failed to build plugin");
211 let mut query = json!({
212 "origin_x": -105.11011135094863,
213 "origin_y": 39.83906153425838
214 });
215
216 process_inject(&plugin, &mut query).expect("failed to run plugin");
217 let value = query.get(&key).expect("test failed: key was not set");
218 let value_number = value.as_i64().expect("test failed: value was not a number");
219 assert_eq!(
220 value_number, 5000,
221 "test failed: value stored in GeoJSON with matching location was not injected"
222 )
223 }
224
225 fn test_geojson_filepath() -> String {
226 let spatial_input_filepath = Path::new(env!("CARGO_MANIFEST_DIR"))
227 .join("src")
228 .join("plugin")
229 .join("input")
230 .join("default")
231 .join("inject")
232 .join("test")
233 .join("test.geojson");
234 let path_str = spatial_input_filepath
235 .to_str()
236 .expect("test invariant failed: unable to convert filepath to string");
237 path_str.to_string()
238 }
239
240 fn test_kv_conf() -> Vec<InjectInputPlugin> {
241 let kv_conf_filepath = Path::new(env!("CARGO_MANIFEST_DIR"))
242 .join("src")
243 .join("plugin")
244 .join("input")
245 .join("default")
246 .join("inject")
247 .join("test")
248 .join("test_inject.toml");
249 let conf_source = config::File::from(kv_conf_filepath);
250
251 let config_toml = Config::builder()
252 .add_source(conf_source)
253 .build()
254 .expect("test invariant failed");
255 let config_json = config_toml
256 .clone()
257 .try_deserialize::<serde_json::Value>()
258 .expect("test invariant failed");
259 let input_plugin_array = config_json
260 .get("input_plugin")
261 .expect("TOML file should have an 'input_plugin' key")
262 .clone();
263 let array = input_plugin_array
264 .as_array()
265 .expect("key input_plugin should be an array");
266 let plugins = array
267 .iter()
268 .map(|conf| {
269 let ipc = serde_json::from_value::<InjectPluginConfig>(conf.clone())
270 .unwrap_or_else(|_| {
271 panic!(
272 "'input_plugin' entry should be valid: {}",
273 serde_json::to_string(&conf).unwrap_or_default()
274 )
275 });
276 ipc.build().unwrap_or_else(|_| {
277 panic!(
278 "InjectPluginConfig.build failed: {}",
279 serde_json::to_string(&conf).unwrap_or_default()
280 )
281 })
282 })
283 .collect_vec();
284 plugins
285 }
286
287 fn setup_spatial(
288 source_key: &Option<String>,
289 key: &str,
290 default: &Option<Value>,
291 ) -> InjectInputPlugin {
292 let spatial_input_file = test_geojson_filepath();
293 let conf = InjectPluginConfig::SpatialKeyValue(SpatialInjectPlugin {
294 spatial_input_file,
295 source_key: source_key.clone(),
296 key: key.to_owned(),
297 write_mode: WriteMode::Overwrite,
298 orientation: CoordinateOrientation::Origin,
299 default: default.clone(),
300 });
301
302 conf.build().expect("test invariant failed")
303 }
304}