strut_rabbitmq/util/
coerce.rs

1use lapin::types::{AMQPValue, ByteArray, LongString, ShortString};
2
3/// The smallest integer that is continuously representable with an `f32`.
4pub const MIN_CONT_INT_F32: i32 = -16_777_216;
5/// The largest integer that is continuously representable with an `f32`.
6pub const MAX_CONT_INT_F32: i32 = 16_777_216;
7
8/// The smallest integer that is continuously representable with an `f64`.
9pub const MIN_CONT_INT_F64: i64 = -9_007_199_254_740_992;
10/// The largest integer that is continuously representable with an `f64`.
11pub const MAX_CONT_INT_F64: i64 = 9_007_199_254_740_992;
12
13/// Artificial trait implemented for a few types like [`AMQPValue`] or
14/// [`ShortString`] to allow conveniently coercing them into standard Rust
15/// types.
16pub trait Coerce<'a, T> {
17    /// Flexibly generates a value of type `T` that is inferred from this object.
18    fn coerce(&'a self) -> Option<T>;
19}
20
21/// Implement [`Coerce`] for [`AMQPValue`].
22const _: () = {
23    impl<'a> Coerce<'a, &'a str> for AMQPValue {
24        fn coerce(&'a self) -> Option<&'a str> {
25            match self {
26                Self::ShortString(s) => s.coerce(),
27                _ => None,
28            }
29        }
30    }
31
32    impl Coerce<'_, String> for AMQPValue {
33        fn coerce(&self) -> Option<String> {
34            match self {
35                Self::Boolean(value) => Some((*value).to_string()),
36                Self::ShortShortUInt(u) => Some((*u).to_string()),
37                Self::ShortUInt(u) => Some((*u).to_string()),
38                Self::LongUInt(u) => Some((*u).to_string()),
39                Self::Timestamp(u) => Some((*u).to_string()),
40                Self::ShortShortInt(i) => Some((*i).to_string()),
41                Self::ShortInt(i) => Some((*i).to_string()),
42                Self::LongInt(i) => Some((*i).to_string()),
43                Self::LongLongInt(i) => Some((*i).to_string()),
44                Self::ShortString(s) => s.coerce(),
45                Self::LongString(s) => s.coerce(),
46                Self::ByteArray(s) => s.coerce(),
47                _ => None,
48            }
49        }
50    }
51
52    impl Coerce<'_, bool> for AMQPValue {
53        fn coerce(&self) -> Option<bool> {
54            match self {
55                Self::Boolean(value) => Some(*value),
56                Self::ShortShortUInt(u) => Some(*u != 0),
57                Self::ShortUInt(u) => Some(*u != 0),
58                Self::LongUInt(u) => Some(*u != 0),
59                Self::Timestamp(u) => Some(*u != 0),
60                Self::ShortShortInt(i) => Some(*i != 0),
61                Self::ShortInt(i) => Some(*i != 0),
62                Self::LongInt(i) => Some(*i != 0),
63                Self::LongLongInt(i) => Some(*i != 0),
64                Self::ShortString(s) => s.coerce(),
65                Self::LongString(s) => s.coerce(),
66                Self::ByteArray(s) => s.coerce(),
67                _ => None,
68            }
69        }
70    }
71
72    impl Coerce<'_, i8> for AMQPValue {
73        fn coerce(&self) -> Option<i8> {
74            match self {
75                Self::ShortShortUInt(u) => (*u).try_into().ok(),
76                Self::ShortUInt(u) => (*u).try_into().ok(),
77                Self::LongUInt(u) => (*u).try_into().ok(),
78                Self::Timestamp(u) => (*u).try_into().ok(),
79                Self::ShortShortInt(i) => Some(*i),
80                Self::ShortInt(i) => (*i).try_into().ok(),
81                Self::LongInt(i) => (*i).try_into().ok(),
82                Self::LongLongInt(i) => (*i).try_into().ok(),
83                Self::ShortString(s) => s.coerce(),
84                Self::LongString(s) => s.coerce(),
85                Self::ByteArray(s) => s.coerce(),
86                _ => None,
87            }
88        }
89    }
90
91    impl Coerce<'_, i16> for AMQPValue {
92        fn coerce(&self) -> Option<i16> {
93            match self {
94                Self::ShortShortUInt(u) => Some(*u as i16),
95                Self::ShortUInt(u) => (*u).try_into().ok(),
96                Self::LongUInt(u) => (*u).try_into().ok(),
97                Self::Timestamp(u) => (*u).try_into().ok(),
98                Self::ShortShortInt(i) => Some(*i as i16),
99                Self::ShortInt(i) => Some(*i),
100                Self::LongInt(i) => (*i).try_into().ok(),
101                Self::LongLongInt(i) => (*i).try_into().ok(),
102                Self::ShortString(s) => s.coerce(),
103                Self::LongString(s) => s.coerce(),
104                Self::ByteArray(s) => s.coerce(),
105                _ => None,
106            }
107        }
108    }
109
110    impl Coerce<'_, i32> for AMQPValue {
111        fn coerce(&self) -> Option<i32> {
112            match self {
113                Self::ShortShortUInt(u) => Some(*u as i32),
114                Self::ShortUInt(u) => Some(*u as i32),
115                Self::LongUInt(u) => (*u).try_into().ok(),
116                Self::Timestamp(u) => (*u).try_into().ok(),
117                Self::ShortShortInt(i) => Some(*i as i32),
118                Self::ShortInt(i) => Some(*i as i32),
119                Self::LongInt(i) => Some(*i),
120                Self::LongLongInt(i) => (*i).try_into().ok(),
121                Self::ShortString(s) => s.coerce(),
122                Self::LongString(s) => s.coerce(),
123                Self::ByteArray(s) => s.coerce(),
124                _ => None,
125            }
126        }
127    }
128
129    impl Coerce<'_, i64> for AMQPValue {
130        fn coerce(&self) -> Option<i64> {
131            match self {
132                Self::ShortShortUInt(u) => Some(*u as i64),
133                Self::ShortUInt(u) => Some(*u as i64),
134                Self::LongUInt(u) => Some(*u as i64),
135                Self::Timestamp(u) => (*u).try_into().ok(),
136                Self::ShortShortInt(i) => Some(*i as i64),
137                Self::ShortInt(i) => Some(*i as i64),
138                Self::LongInt(i) => Some(*i as i64),
139                Self::LongLongInt(i) => Some(*i),
140                Self::ShortString(s) => s.coerce(),
141                Self::LongString(s) => s.coerce(),
142                Self::ByteArray(s) => s.coerce(),
143                _ => None,
144            }
145        }
146    }
147
148    impl Coerce<'_, isize> for AMQPValue {
149        fn coerce(&self) -> Option<isize> {
150            match self {
151                Self::ShortShortUInt(u) => Some(*u as isize),
152                Self::ShortUInt(u) => (*u).try_into().ok(),
153                Self::LongUInt(u) => (*u).try_into().ok(),
154                Self::Timestamp(u) => (*u).try_into().ok(),
155                Self::ShortShortInt(i) => Some(*i as isize),
156                Self::ShortInt(i) => Some(*i as isize),
157                Self::LongInt(i) => Some(*i as isize),
158                Self::LongLongInt(i) => (*i).try_into().ok(),
159                Self::ShortString(s) => s.coerce(),
160                Self::LongString(s) => s.coerce(),
161                Self::ByteArray(s) => s.coerce(),
162                _ => None,
163            }
164        }
165    }
166
167    impl Coerce<'_, u8> for AMQPValue {
168        fn coerce(&self) -> Option<u8> {
169            match self {
170                Self::ShortShortUInt(u) => Some(*u),
171                Self::ShortUInt(u) => (*u).try_into().ok(),
172                Self::LongUInt(u) => (*u).try_into().ok(),
173                Self::Timestamp(u) => (*u).try_into().ok(),
174                Self::ShortShortInt(i) => (*i).try_into().ok(),
175                Self::ShortInt(i) => (*i).try_into().ok(),
176                Self::LongInt(i) => (*i).try_into().ok(),
177                Self::LongLongInt(i) => (*i).try_into().ok(),
178                Self::ShortString(s) => s.coerce(),
179                Self::LongString(s) => s.coerce(),
180                Self::ByteArray(s) => s.coerce(),
181                _ => None,
182            }
183        }
184    }
185
186    impl Coerce<'_, u16> for AMQPValue {
187        fn coerce(&self) -> Option<u16> {
188            match self {
189                Self::ShortShortUInt(u) => Some(*u as u16),
190                Self::ShortUInt(u) => Some(*u),
191                Self::LongUInt(u) => (*u).try_into().ok(),
192                Self::Timestamp(u) => (*u).try_into().ok(),
193                Self::ShortShortInt(i) => (*i).try_into().ok(),
194                Self::ShortInt(i) => (*i).try_into().ok(),
195                Self::LongInt(i) => (*i).try_into().ok(),
196                Self::LongLongInt(i) => (*i).try_into().ok(),
197                Self::ShortString(s) => s.coerce(),
198                Self::LongString(s) => s.coerce(),
199                Self::ByteArray(s) => s.coerce(),
200                _ => None,
201            }
202        }
203    }
204
205    impl Coerce<'_, u32> for AMQPValue {
206        fn coerce(&self) -> Option<u32> {
207            match self {
208                Self::ShortShortUInt(u) => Some(*u as u32),
209                Self::ShortUInt(u) => Some(*u as u32),
210                Self::LongUInt(u) => Some(*u),
211                Self::Timestamp(u) => (*u).try_into().ok(),
212                Self::ShortShortInt(i) => (*i).try_into().ok(),
213                Self::ShortInt(i) => (*i).try_into().ok(),
214                Self::LongInt(i) => (*i).try_into().ok(),
215                Self::LongLongInt(i) => (*i).try_into().ok(),
216                Self::ShortString(s) => s.coerce(),
217                Self::LongString(s) => s.coerce(),
218                Self::ByteArray(s) => s.coerce(),
219                _ => None,
220            }
221        }
222    }
223
224    impl Coerce<'_, u64> for AMQPValue {
225        fn coerce(&self) -> Option<u64> {
226            match self {
227                Self::ShortShortUInt(u) => Some(*u as u64),
228                Self::ShortUInt(u) => Some(*u as u64),
229                Self::LongUInt(u) => Some(*u as u64),
230                Self::Timestamp(u) => Some(*u),
231                Self::ShortShortInt(i) => (*i).try_into().ok(),
232                Self::ShortInt(i) => (*i).try_into().ok(),
233                Self::LongInt(i) => (*i).try_into().ok(),
234                Self::LongLongInt(i) => (*i).try_into().ok(),
235                Self::ShortString(s) => s.coerce(),
236                Self::LongString(s) => s.coerce(),
237                Self::ByteArray(s) => s.coerce(),
238                _ => None,
239            }
240        }
241    }
242
243    impl Coerce<'_, usize> for AMQPValue {
244        fn coerce(&self) -> Option<usize> {
245            match self {
246                Self::ShortShortUInt(u) => Some(*u as usize),
247                Self::ShortUInt(u) => Some(*u as usize),
248                Self::LongUInt(u) => Some(*u as usize),
249                Self::Timestamp(u) => (*u).try_into().ok(),
250                Self::ShortShortInt(i) => (*i).try_into().ok(),
251                Self::ShortInt(i) => (*i).try_into().ok(),
252                Self::LongInt(i) => (*i).try_into().ok(),
253                Self::LongLongInt(i) => (*i).try_into().ok(),
254                Self::ShortString(s) => s.coerce(),
255                Self::LongString(s) => s.coerce(),
256                Self::ByteArray(s) => s.coerce(),
257                _ => None,
258            }
259        }
260    }
261
262    impl Coerce<'_, f32> for AMQPValue {
263        fn coerce(&'_ self) -> Option<f32> {
264            match self {
265                Self::ShortShortUInt(u) => Some(*u as f32),
266                Self::ShortUInt(u) => Some(*u as f32),
267                Self::LongUInt(u) => {
268                    if (*u) <= (MAX_CONT_INT_F32 as u32) {
269                        Some(*u as f32)
270                    } else {
271                        None
272                    }
273                }
274                Self::Timestamp(u) => {
275                    if (*u) <= (MAX_CONT_INT_F32 as u64) {
276                        Some(*u as f32)
277                    } else {
278                        None
279                    }
280                }
281                Self::ShortShortInt(i) => Some(*i as f32),
282                Self::ShortInt(i) => Some(*i as f32),
283                Self::LongInt(i) => {
284                    if (*i) >= MIN_CONT_INT_F32 && (*i) <= MAX_CONT_INT_F32 {
285                        Some(*i as f32)
286                    } else {
287                        None
288                    }
289                }
290                Self::LongLongInt(i) => {
291                    if (*i) >= (MIN_CONT_INT_F32 as i64) && (*i) <= (MAX_CONT_INT_F32 as i64) {
292                        Some(*i as f32)
293                    } else {
294                        None
295                    }
296                }
297                Self::ShortString(s) => s.coerce(),
298                Self::LongString(s) => s.coerce(),
299                Self::ByteArray(s) => s.coerce(),
300                _ => None,
301            }
302        }
303    }
304
305    impl Coerce<'_, f64> for AMQPValue {
306        fn coerce(&'_ self) -> Option<f64> {
307            match self {
308                Self::ShortShortUInt(u) => Some(*u as f64),
309                Self::ShortUInt(u) => Some(*u as f64),
310                Self::LongUInt(u) => Some(*u as f64),
311                Self::Timestamp(u) => {
312                    if (*u) <= (MAX_CONT_INT_F64 as u64) {
313                        Some(*u as f64)
314                    } else {
315                        None
316                    }
317                }
318                Self::ShortShortInt(i) => Some(*i as f64),
319                Self::ShortInt(i) => Some(*i as f64),
320                Self::LongInt(i) => Some(*i as f64),
321                Self::LongLongInt(i) => {
322                    if (*i) >= MIN_CONT_INT_F64 && (*i) <= MAX_CONT_INT_F64 {
323                        Some(*i as f64)
324                    } else {
325                        None
326                    }
327                }
328                Self::ShortString(s) => s.coerce(),
329                Self::LongString(s) => s.coerce(),
330                Self::ByteArray(s) => s.coerce(),
331                _ => None,
332            }
333        }
334    }
335};
336
337/// Implement [`Coerce`] for [`ShortString`].
338const _: () = {
339    impl<'a> Coerce<'a, &'a str> for ShortString {
340        fn coerce(&'a self) -> Option<&'a str> {
341            Some(self.as_str())
342        }
343    }
344
345    impl Coerce<'_, String> for ShortString {
346        fn coerce(&self) -> Option<String> {
347            Some(self.to_string())
348        }
349    }
350
351    impl Coerce<'_, bool> for ShortString {
352        fn coerce(&self) -> Option<bool> {
353            parse_bool(self.as_str())
354        }
355    }
356
357    impl Coerce<'_, i8> for ShortString {
358        fn coerce(&self) -> Option<i8> {
359            self.as_str().parse::<i8>().ok()
360        }
361    }
362
363    impl Coerce<'_, i16> for ShortString {
364        fn coerce(&self) -> Option<i16> {
365            self.as_str().parse::<i16>().ok()
366        }
367    }
368
369    impl Coerce<'_, i32> for ShortString {
370        fn coerce(&self) -> Option<i32> {
371            self.as_str().parse::<i32>().ok()
372        }
373    }
374
375    impl Coerce<'_, i64> for ShortString {
376        fn coerce(&self) -> Option<i64> {
377            self.as_str().parse::<i64>().ok()
378        }
379    }
380
381    impl Coerce<'_, isize> for ShortString {
382        fn coerce(&self) -> Option<isize> {
383            self.as_str().parse::<isize>().ok()
384        }
385    }
386
387    impl Coerce<'_, u8> for ShortString {
388        fn coerce(&self) -> Option<u8> {
389            self.as_str().parse::<u8>().ok()
390        }
391    }
392
393    impl Coerce<'_, u16> for ShortString {
394        fn coerce(&self) -> Option<u16> {
395            self.as_str().parse::<u16>().ok()
396        }
397    }
398
399    impl Coerce<'_, u32> for ShortString {
400        fn coerce(&self) -> Option<u32> {
401            self.as_str().parse::<u32>().ok()
402        }
403    }
404
405    impl Coerce<'_, u64> for ShortString {
406        fn coerce(&self) -> Option<u64> {
407            self.as_str().parse::<u64>().ok()
408        }
409    }
410
411    impl Coerce<'_, usize> for ShortString {
412        fn coerce(&self) -> Option<usize> {
413            self.as_str().parse::<usize>().ok()
414        }
415    }
416
417    impl Coerce<'_, f32> for ShortString {
418        fn coerce(&self) -> Option<f32> {
419            self.as_str().parse::<f32>().ok()
420        }
421    }
422
423    impl Coerce<'_, f64> for ShortString {
424        fn coerce(&self) -> Option<f64> {
425            self.as_str().parse::<f64>().ok()
426        }
427    }
428};
429
430/// Implement [`Coerce`] for [`LongString`].
431const _: () = {
432    impl Coerce<'_, String> for LongString {
433        fn coerce(&self) -> Option<String> {
434            Some(self.to_string())
435        }
436    }
437
438    impl Coerce<'_, bool> for LongString {
439        fn coerce(&self) -> Option<bool> {
440            parse_bool(&self.to_string())
441        }
442    }
443
444    impl Coerce<'_, i8> for LongString {
445        fn coerce(&self) -> Option<i8> {
446            self.to_string().parse::<i8>().ok()
447        }
448    }
449
450    impl Coerce<'_, i16> for LongString {
451        fn coerce(&self) -> Option<i16> {
452            self.to_string().parse::<i16>().ok()
453        }
454    }
455
456    impl Coerce<'_, i32> for LongString {
457        fn coerce(&self) -> Option<i32> {
458            self.to_string().parse::<i32>().ok()
459        }
460    }
461
462    impl Coerce<'_, i64> for LongString {
463        fn coerce(&self) -> Option<i64> {
464            self.to_string().parse::<i64>().ok()
465        }
466    }
467
468    impl Coerce<'_, isize> for LongString {
469        fn coerce(&self) -> Option<isize> {
470            self.to_string().parse::<isize>().ok()
471        }
472    }
473
474    impl Coerce<'_, u8> for LongString {
475        fn coerce(&self) -> Option<u8> {
476            self.to_string().parse::<u8>().ok()
477        }
478    }
479
480    impl Coerce<'_, u16> for LongString {
481        fn coerce(&self) -> Option<u16> {
482            self.to_string().parse::<u16>().ok()
483        }
484    }
485
486    impl Coerce<'_, u32> for LongString {
487        fn coerce(&self) -> Option<u32> {
488            self.to_string().parse::<u32>().ok()
489        }
490    }
491
492    impl Coerce<'_, u64> for LongString {
493        fn coerce(&self) -> Option<u64> {
494            self.to_string().parse::<u64>().ok()
495        }
496    }
497
498    impl Coerce<'_, usize> for LongString {
499        fn coerce(&self) -> Option<usize> {
500            self.to_string().parse::<usize>().ok()
501        }
502    }
503
504    impl Coerce<'_, f32> for LongString {
505        fn coerce(&self) -> Option<f32> {
506            self.to_string().parse::<f32>().ok()
507        }
508    }
509
510    impl Coerce<'_, f64> for LongString {
511        fn coerce(&self) -> Option<f64> {
512            self.to_string().parse::<f64>().ok()
513        }
514    }
515};
516
517/// Implement [`Coerce`] for [`ByteArray`].
518const _: () = {
519    impl Coerce<'_, String> for ByteArray {
520        fn coerce(&self) -> Option<String> {
521            Some(String::from_utf8_lossy(self.as_slice()).to_string())
522        }
523    }
524
525    impl Coerce<'_, bool> for ByteArray {
526        fn coerce(&self) -> Option<bool> {
527            parse_bool(&String::from_utf8_lossy(self.as_slice()))
528        }
529    }
530
531    impl Coerce<'_, i8> for ByteArray {
532        fn coerce(&self) -> Option<i8> {
533            String::from_utf8_lossy(self.as_slice()).parse::<i8>().ok()
534        }
535    }
536
537    impl Coerce<'_, i16> for ByteArray {
538        fn coerce(&self) -> Option<i16> {
539            String::from_utf8_lossy(self.as_slice()).parse::<i16>().ok()
540        }
541    }
542
543    impl Coerce<'_, i32> for ByteArray {
544        fn coerce(&self) -> Option<i32> {
545            String::from_utf8_lossy(self.as_slice()).parse::<i32>().ok()
546        }
547    }
548
549    impl Coerce<'_, i64> for ByteArray {
550        fn coerce(&self) -> Option<i64> {
551            String::from_utf8_lossy(self.as_slice()).parse::<i64>().ok()
552        }
553    }
554
555    impl Coerce<'_, isize> for ByteArray {
556        fn coerce(&self) -> Option<isize> {
557            String::from_utf8_lossy(self.as_slice())
558                .parse::<isize>()
559                .ok()
560        }
561    }
562
563    impl Coerce<'_, u8> for ByteArray {
564        fn coerce(&self) -> Option<u8> {
565            String::from_utf8_lossy(self.as_slice()).parse::<u8>().ok()
566        }
567    }
568
569    impl Coerce<'_, u16> for ByteArray {
570        fn coerce(&self) -> Option<u16> {
571            String::from_utf8_lossy(self.as_slice()).parse::<u16>().ok()
572        }
573    }
574
575    impl Coerce<'_, u32> for ByteArray {
576        fn coerce(&self) -> Option<u32> {
577            String::from_utf8_lossy(self.as_slice()).parse::<u32>().ok()
578        }
579    }
580
581    impl Coerce<'_, u64> for ByteArray {
582        fn coerce(&self) -> Option<u64> {
583            String::from_utf8_lossy(self.as_slice()).parse::<u64>().ok()
584        }
585    }
586
587    impl Coerce<'_, usize> for ByteArray {
588        fn coerce(&self) -> Option<usize> {
589            String::from_utf8_lossy(self.as_slice())
590                .parse::<usize>()
591                .ok()
592        }
593    }
594
595    impl Coerce<'_, f32> for ByteArray {
596        fn coerce(&self) -> Option<f32> {
597            String::from_utf8_lossy(self.as_slice()).parse::<f32>().ok()
598        }
599    }
600
601    impl Coerce<'_, f64> for ByteArray {
602        fn coerce(&self) -> Option<f64> {
603            String::from_utf8_lossy(self.as_slice()).parse::<f64>().ok()
604        }
605    }
606};
607
608/// Internal helper for parsing a human-readable string into a `bool`.
609fn parse_bool(input: &str) -> Option<bool> {
610    match input.len() {
611        0 => Some(false), // empty -> false
612
613        1 => {
614            // single-char: 1/0, t/f, y/n
615            let b = input.as_bytes()[0];
616            match b {
617                b'1' => Some(true),
618                b'0' => Some(false),
619                _ => match b.to_ascii_lowercase() {
620                    b't' | b'y' => Some(true),
621                    b'f' | b'n' => Some(false),
622                    _ => None,
623                },
624            }
625        }
626
627        2 if input.eq_ignore_ascii_case("on") => Some(true),
628        2 if input.eq_ignore_ascii_case("no") => Some(false),
629
630        3 if input.eq_ignore_ascii_case("yes") => Some(true),
631        3 if input.eq_ignore_ascii_case("off") => Some(false),
632
633        4 if input.eq_ignore_ascii_case("true") => Some(true),
634        5 if input.eq_ignore_ascii_case("false") => Some(false),
635
636        _ => None,
637    }
638}
639
640#[cfg(test)]
641mod tests {
642    use super::parse_bool;
643
644    #[test]
645    fn true_variants() {
646        let trues = [
647            "", //–– actually false, but we test empty separately below
648            "true", "TRUE", "True", "t", "T", "1", "yes", "YES", "Yes", "y", "Y", "on", "ON", "On",
649        ];
650        for &s in &trues[1..] {
651            assert_eq!(parse_bool(s), Some(true), "should parse {:?} as true", s);
652        }
653    }
654
655    #[test]
656    fn false_variants() {
657        let falses = [
658            "", // empty → false
659            "false", "FALSE", "False", "f", "F", "0", "no", "NO", "No", "n", "N", "off", "OFF",
660            "Off",
661        ];
662        for &s in &falses {
663            assert_eq!(parse_bool(s), Some(false), "should parse {:?} as false", s);
664        }
665    }
666
667    #[test]
668    fn rejects_invalid() {
669        let bad = [
670            "2", "on?", "off!", "tru", "fals", "yep", "nah", "ye", "nOpe", "yes!", " false ",
671        ];
672        for &s in &bad {
673            assert_eq!(parse_bool(s), None, "should reject {:?}", s);
674        }
675    }
676}