1use std::cmp::Ordering;
2use std::future::Future;
3use std::pin::Pin;
4use std::sync::Arc;
5use std::task::{Context, Poll};
6
7use tower::Service;
8
9use camel_api::{CamelError, Exchange};
10
11#[derive(Debug, Clone)]
15pub struct SortKey(pub serde_json::Value);
16
17impl Ord for SortKey {
18 fn cmp(&self, other: &Self) -> Ordering {
19 use serde_json::Value::*;
20 match (&self.0, &other.0) {
21 (Null, Null) => Ordering::Equal,
22 (Null, _) => Ordering::Less,
23 (_, Null) => Ordering::Greater,
24 (Bool(a), Bool(b)) => a.cmp(b),
25 (Bool(_), Number(_)) | (Bool(_), String(_)) => Ordering::Less,
26 (Number(_), Bool(_)) | (String(_), Bool(_)) => Ordering::Greater,
27 (Number(a), Number(b)) => {
30 let af = a.as_f64().unwrap_or(f64::INFINITY);
31 let bf = b.as_f64().unwrap_or(f64::INFINITY);
32 af.partial_cmp(&bf)
33 .unwrap_or_else(|| af.is_nan().cmp(&bf.is_nan()))
34 }
35 (Number(_), String(_)) => Ordering::Less,
36 (String(_), Number(_)) => Ordering::Greater,
37 (String(a), String(b)) => a.cmp(b),
38 _ => Ordering::Equal, }
40 }
41}
42
43impl PartialOrd for SortKey {
44 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
45 Some(self.cmp(other))
46 }
47}
48
49impl PartialEq for SortKey {
50 fn eq(&self, other: &Self) -> bool {
51 self.cmp(other) == Ordering::Equal
52 }
53}
54
55impl Eq for SortKey {}
56
57#[allow(clippy::type_complexity)]
65#[derive(Clone)]
66pub enum SortKeySource {
67 Sync(Arc<dyn Fn(&serde_json::Value) -> Result<SortKey, CamelError> + Send + Sync>),
69 Async(Arc<dyn Fn(&serde_json::Value) -> camel_api::BoxValueFuture + Send + Sync>),
71}
72
73impl SortKeySource {
74 pub async fn key(&self, value: &serde_json::Value) -> Result<SortKey, CamelError> {
76 match self {
77 Self::Sync(f) => f(value),
78 Self::Async(f) => {
79 let evaluated = f(value).await?;
80 match evaluated {
81 serde_json::Value::Array(_) | serde_json::Value::Object(_) => {
82 Err(CamelError::ProcessorError(
83 "sort expression returned a non-scalar value (array/object); expected null/bool/number/string".into(),
84 ))
85 }
86 scalar => Ok(SortKey(scalar)),
87 }
88 }
89 }
90 }
91}
92
93#[derive(Clone)]
98pub struct SortService {
99 expression: SortKeySource,
100 reverse: bool,
101}
102
103impl SortService {
104 pub fn new(expression: SortKeySource, reverse: bool) -> Self {
105 Self {
106 expression,
107 reverse,
108 }
109 }
110}
111
112impl Service<Exchange> for SortService {
113 type Response = Exchange;
114 type Error = CamelError;
115 type Future = Pin<Box<dyn Future<Output = Result<Exchange, CamelError>> + Send>>;
116
117 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
118 Poll::Ready(Ok(()))
119 }
120
121 fn call(&mut self, mut exchange: Exchange) -> Self::Future {
122 let expression = self.expression.clone();
123 let reverse = self.reverse;
124 Box::pin(async move {
125 let array = match std::mem::take(&mut exchange.input.body) {
126 camel_api::body::Body::Json(serde_json::Value::Array(arr)) => arr,
127 _ => {
128 return Err(CamelError::ProcessorError(
129 "sort requires an array body (Body::Json(Value::Array))".into(),
130 ));
131 }
132 };
133
134 let mut indexed: Vec<(SortKey, serde_json::Value)> = Vec::with_capacity(array.len());
135 for element in array {
136 let key = expression.key(&element).await?;
137 indexed.push((key, element));
138 }
139
140 if reverse {
141 indexed.sort_by(|a, b| b.0.cmp(&a.0));
142 } else {
143 indexed.sort_by(|a, b| a.0.cmp(&b.0));
144 }
145
146 let sorted: Vec<serde_json::Value> = indexed.into_iter().map(|(_, v)| v).collect();
147 exchange.input.body = camel_api::body::Body::Json(serde_json::Value::Array(sorted));
148 Ok(exchange)
149 })
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156 use camel_api::{Exchange, Message, body::Body};
157 use serde_json::json;
158 use tower::ServiceExt;
159
160 #[tokio::test]
161 async fn ascending_numeric_sort() {
162 let exchange = Exchange::new(Message::new(Body::Json(json!([3, 1, 2]))));
163 let expr = SortKeySource::Sync(Arc::new(|v| Ok(SortKey(v.clone()))));
164 let svc = SortService::new(expr, false);
165 let result = svc.oneshot(exchange).await.unwrap();
166 assert_eq!(result.input.body, Body::Json(json!([1, 2, 3])));
167 }
168
169 #[tokio::test]
170 async fn descending_numeric_sort() {
171 let exchange = Exchange::new(Message::new(Body::Json(json!([3, 1, 2]))));
172 let expr = SortKeySource::Sync(Arc::new(|v| Ok(SortKey(v.clone()))));
173 let svc = SortService::new(expr, true);
174 let result = svc.oneshot(exchange).await.unwrap();
175 assert_eq!(result.input.body, Body::Json(json!([3, 2, 1])));
176 }
177
178 #[tokio::test]
179 async fn string_sort() {
180 let exchange = Exchange::new(Message::new(Body::Json(json!([
181 "banana", "apple", "cherry"
182 ]))));
183 let expr = SortKeySource::Sync(Arc::new(|v| Ok(SortKey(v.clone()))));
184 let svc = SortService::new(expr, false);
185 let result = svc.oneshot(exchange).await.unwrap();
186 assert_eq!(
187 result.input.body,
188 Body::Json(json!(["apple", "banana", "cherry"]))
189 );
190 }
191
192 #[tokio::test]
193 async fn empty_array_passthrough() {
194 let exchange = Exchange::new(Message::new(Body::Json(json!([]))));
195 let expr = SortKeySource::Sync(Arc::new(|v| Ok(SortKey(v.clone()))));
196 let svc = SortService::new(expr, false);
197 let result = svc.oneshot(exchange).await.unwrap();
198 assert_eq!(result.input.body, Body::Json(json!([])));
199 }
200
201 #[tokio::test]
202 async fn non_array_body_errors() {
203 let exchange = Exchange::new(Message::new(Body::Text("hello".to_string())));
204 let expr = SortKeySource::Sync(Arc::new(|v| Ok(SortKey(v.clone()))));
205 let svc = SortService::new(expr, false);
206 let result = svc.oneshot(exchange).await;
207 assert!(matches!(result, Err(CamelError::ProcessorError(_))));
208 }
209
210 #[tokio::test]
211 async fn array_key_errors() {
212 let exchange = Exchange::new(Message::new(Body::Json(json!([[1, 2], 3]))));
213 let expr = SortKeySource::Sync(Arc::new(|v| {
214 if v.is_array() {
215 Err(CamelError::ProcessorError("array key rejected".into()))
216 } else {
217 Ok(SortKey(v.clone()))
218 }
219 }));
220 let svc = SortService::new(expr, false);
221 let result = svc.oneshot(exchange).await;
222 assert!(result.is_err());
223 }
224
225 #[tokio::test]
226 async fn mixed_type_sort_key_order() {
227 let exchange = Exchange::new(Message::new(Body::Json(json!([
229 "str", null, false, 42, true, 1
230 ]))));
231 let expr = SortKeySource::Sync(Arc::new(|v| Ok(SortKey(v.clone()))));
232 let svc = SortService::new(expr, false);
233 let result = svc.oneshot(exchange).await.unwrap();
234 assert_eq!(
236 result.input.body,
237 Body::Json(json!([null, false, true, 1, 42, "str"]))
238 );
239 }
240
241 #[tokio::test]
242 async fn sort_async_error_propagates() {
243 use camel_api::BoxValueFuture;
244
245 let source = SortKeySource::Async(Arc::new(|_: &serde_json::Value| {
246 Box::pin(async { Err(CamelError::ProcessorError("sort boom".into())) })
247 as BoxValueFuture
248 }));
249 let exchange = Exchange::new(Message::new(Body::Json(json!([3, 1, 2]))));
250 let svc = SortService::new(source, false);
251
252 let result = svc.oneshot(exchange).await;
253 assert!(
254 matches!(result, Err(CamelError::ProcessorError(ref e)) if e.contains("sort boom")),
255 "an async key-expression failure must propagate"
256 );
257 }
258}