over_there/core/msg/content/request/
transform.rs1use crate::core::{Reply, Request};
2use derive_more::{Display, Error};
3use jsonpath_lib as jsonpath;
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Display, Error)]
10pub enum TransformRequestError {
11 RequestToJsonFailed(serde_json::Error),
12 JsonToRequestFailed(serde_json::Error),
13 #[display(fmt = "{:?}", _0)]
14 ExtractingReplyValueFailed(#[error(ignore)] jsonpath::JsonPathError),
15 ReplyValueMissing {
16 path: String,
17 },
18 ReplyValueNotScalar {
19 path: String,
20 },
21 #[display(fmt = "{:?}", _0)]
22 ReplacementFailed(#[error(ignore)] jsonpath::JsonPathError),
23}
24
25#[derive(JsonSchema, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
28pub struct LazilyTransformedRequest {
29 pub rules: Vec<TransformRule>,
31
32 pub raw_request: Request,
34}
35
36impl LazilyTransformedRequest {
37 pub fn new(raw_request: Request, rules: Vec<TransformRule>) -> Self {
38 Self { rules, raw_request }
39 }
40
41 pub fn into_raw_request(self) -> Request {
43 self.raw_request
44 }
45
46 pub fn transform_with_reply(
49 &self,
50 reply: &Reply,
51 ) -> Result<Request, TransformRequestError> {
52 let mut value = serde_json::to_value(&self.raw_request)
53 .map_err(TransformRequestError::RequestToJsonFailed)?;
54 let reply_value = serde_json::to_value(reply)
55 .map_err(TransformRequestError::RequestToJsonFailed)?;
56
57 for rule in self.rules.iter() {
58 let mut new_values = jsonpath::select(&reply_value, &rule.value)
61 .map_err(TransformRequestError::ExtractingReplyValueFailed)?;
62 if new_values.is_empty() {
63 return Err(TransformRequestError::ReplyValueMissing {
64 path: rule.value.clone(),
65 });
66 } else if new_values.len() > 1 {
67 return Err(TransformRequestError::ReplyValueNotScalar {
68 path: rule.value.clone(),
69 });
70 }
71 let new_value = new_values.drain(0..=0).last();
72
73 value = jsonpath::replace_with(value, &rule.path, &mut |_| {
74 new_value.cloned()
75 })
76 .map_err(TransformRequestError::ReplacementFailed)?;
77 }
78
79 serde_json::from_value(value)
80 .map_err(TransformRequestError::JsonToRequestFailed)
81 }
82}
83
84impl crate::core::SchemaInfo for LazilyTransformedRequest {}
85
86#[derive(JsonSchema, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
90pub struct TransformRule {
91 pub path: String,
95
96 pub value: String,
101}
102
103impl crate::core::SchemaInfo for TransformRule {}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use crate::core::reply::{CustomArgs, FileOpenedArgs};
109
110 #[test]
111 fn transform_with_reply_should_fail_if_rule_value_not_found() {
112 let raw_request = Request::ReadFile(Default::default());
113 let reply = Reply::FileOpened(FileOpenedArgs {
114 id: 123,
115 sig: 456,
116 ..Default::default()
117 });
118
119 let lazy_request = LazilyTransformedRequest {
120 raw_request: raw_request.clone(),
121 rules: vec![TransformRule {
122 path: String::from("$.payload.id"),
124
125 value: String::from("$.payload.missing_field"),
127 }],
128 };
129
130 match lazy_request.transform_with_reply(&reply) {
131 Err(TransformRequestError::ReplyValueMissing { .. }) => (),
132 x => panic!("Unexpected request: {:?}", x),
133 }
134 }
135
136 #[test]
137 fn transform_with_reply_should_fail_if_rule_value_not_scalar() {
138 let raw_request = Request::ReadFile(Default::default());
139 let reply = Reply::Custom(CustomArgs {
140 data: vec![0, 1, 2],
141 });
142
143 let lazy_request = LazilyTransformedRequest {
144 raw_request: raw_request.clone(),
145 rules: vec![TransformRule {
146 path: String::from("$.payload.id"),
148
149 value: String::from("$.payload.data[*]"),
151 }],
152 };
153
154 match lazy_request.transform_with_reply(&reply) {
155 Err(TransformRequestError::ReplyValueNotScalar { .. }) => (),
156 x => panic!("Unexpected request: {:?}", x),
157 }
158 }
159
160 #[test]
161 fn transform_with_reply_should_fail_if_rule_value_not_same_type_as_path() {
162 let raw_request = Request::Custom(Default::default());
163 let reply = Reply::FileOpened(FileOpenedArgs {
164 id: 123,
165 sig: 456,
166 ..Default::default()
167 });
168
169 let lazy_request = LazilyTransformedRequest {
170 raw_request: raw_request.clone(),
171 rules: vec![TransformRule {
172 path: String::from("$.payload.data"),
174
175 value: String::from("$.payload.id"),
177 }],
178 };
179
180 match lazy_request.transform_with_reply(&reply) {
181 Err(TransformRequestError::JsonToRequestFailed(_)) => (),
182 x => panic!("Unexpected request: {:?}", x),
183 }
184 }
185
186 #[test]
187 fn transform_with_reply_should_return_raw_request_if_rule_path_missing() {
188 let raw_request = Request::ReadFile(Default::default());
189 let reply = Reply::FileOpened(FileOpenedArgs {
190 id: 123,
191 sig: 456,
192 ..Default::default()
193 });
194
195 let lazy_request = LazilyTransformedRequest {
196 raw_request: raw_request.clone(),
197 rules: vec![TransformRule {
198 path: String::from("$.payload.missing_field"),
200
201 value: String::from("$.payload.id"),
203 }],
204 };
205
206 match lazy_request.transform_with_reply(&reply) {
207 Ok(request) => {
208 assert_eq!(request, raw_request, "Raw request altered")
209 }
210 x => panic!("Unexpected request: {:?}", x),
211 }
212 }
213
214 #[test]
215 fn transform_with_reply_should_succeed_if_able_to_replace_path_with_value()
216 {
217 let raw_request = Request::ReadFile(Default::default());
218 let reply = Reply::FileOpened(FileOpenedArgs {
219 id: 123,
220 sig: 456,
221 ..Default::default()
222 });
223
224 let lazy_request = LazilyTransformedRequest {
225 raw_request: raw_request.clone(),
226 rules: vec![TransformRule {
227 path: String::from("$.payload.id"),
229
230 value: String::from("$.payload.id"),
232 }],
233 };
234
235 let transformed_request = lazy_request
236 .transform_with_reply(&reply)
237 .expect("Failed to transform");
238
239 match transformed_request {
240 Request::ReadFile(args) => {
241 assert_eq!(args.id, 123);
242 assert_ne!(args.sig, 456);
243 }
244 x => panic!("Unexpected request: {:?}", x),
245 }
246 }
247
248 #[test]
249 fn transform_with_reply_should_apply_rules_in_sequence() {
250 let raw_request = Request::ReadFile(Default::default());
251 let reply = Reply::FileOpened(FileOpenedArgs {
252 id: 123,
253 sig: 456,
254 ..Default::default()
255 });
256
257 let lazy_request = LazilyTransformedRequest {
258 raw_request: raw_request.clone(),
259 rules: vec![
260 TransformRule {
261 path: String::from("$.payload.id"),
263
264 value: String::from("$.payload.id"),
266 },
267 TransformRule {
268 path: String::from("$.payload.sig"),
270
271 value: String::from("$.payload.sig"),
273 },
274 ],
275 };
276
277 let transformed_request = lazy_request
278 .transform_with_reply(&reply)
279 .expect("Failed to transform");
280
281 match transformed_request {
282 Request::ReadFile(args) => {
283 assert_eq!(args.id, 123);
284 assert_eq!(args.sig, 456);
285 }
286 x => panic!("Unexpected request: {:?}", x),
287 }
288 }
289}