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