atat/
derive.rs

1use heapless::{String, Vec};
2use serde_at::HexStr;
3
4/// Trait used by [`atat_derive`] to estimate lengths of the serialized commands, at compile time.
5///
6/// [`atat_derive`]: https://crates.io/crates/atat_derive
7pub trait AtatLen {
8    const LEN: usize;
9}
10
11#[cfg(feature = "bytes")]
12impl<const N: usize> AtatLen for heapless_bytes::Bytes<N> {
13    const LEN: usize = N;
14}
15
16macro_rules! impl_length {
17    ($type:ty, $len:expr) => {
18        #[allow(clippy::use_self)]
19        impl AtatLen for $type {
20            const LEN: usize = $len;
21        }
22    };
23}
24
25impl_length!(char, 1);
26impl_length!(bool, 5);
27impl_length!(isize, 19);
28impl_length!(usize, 20);
29impl_length!(u8, 3);
30impl_length!(u16, 5);
31impl_length!(u32, 10);
32impl_length!(u64, 20);
33impl_length!(u128, 39);
34impl_length!(i8, 4);
35impl_length!(i16, 6);
36impl_length!(i32, 11);
37impl_length!(i64, 20);
38impl_length!(i128, 40);
39impl_length!(f32, 42);
40impl_length!(f64, 312);
41
42//       0x   F:F:F:F
43// uN = (2 + (N/2) - 1) * 2 bytes
44impl_length!(HexStr<u8>, 10);
45impl_length!(HexStr<u16>, 18);
46impl_length!(HexStr<u32>, 30);
47impl_length!(HexStr<u64>, 66);
48impl_length!(HexStr<u128>, 130);
49
50impl<const T: usize> AtatLen for String<T> {
51    const LEN: usize = 1 + T + 1;
52}
53
54impl<T: AtatLen> AtatLen for Option<T> {
55    const LEN: usize = T::LEN;
56}
57
58impl<T: AtatLen> AtatLen for &T {
59    const LEN: usize = T::LEN;
60}
61
62impl<T, const L: usize> AtatLen for Vec<T, L>
63where
64    T: AtatLen,
65{
66    const LEN: usize = L * <T as AtatLen>::LEN;
67}
68
69//       0x   F:F:F:F
70// uN = (2 + (N*4) - 1) * 2 bytes
71impl<const L: usize> AtatLen for HexStr<[u8; L]> {
72    const LEN: usize = (2 + L * 4 - 1) * 2;
73}
74
75#[cfg(test)]
76mod tests {
77    use std::convert::TryFrom;
78
79    use crate as atat;
80    use atat::{derive::AtatLen, AtatCmd};
81    use atat_derive::{AtatCmd, AtatEnum, AtatResp};
82    use heapless::{String, Vec};
83    use serde_at::{from_str, to_string, HexStr, SerializeOptions};
84
85    macro_rules! assert_not_impl {
86        ($x:ty, $($t:path),+ $(,)*) => {
87            const _: fn() -> () = || {
88                struct Check<T: ?Sized>(T);
89                trait AmbiguousIfImpl<A> { fn some_item() { } }
90
91                impl<T: ?Sized> AmbiguousIfImpl<()> for Check<T> { }
92                impl<T: ?Sized $(+ $t)*> AmbiguousIfImpl<u8> for Check<T> { }
93
94                <Check::<$x> as AmbiguousIfImpl<_>>::some_item()
95            };
96        };
97    }
98
99    #[derive(Debug, PartialEq, AtatResp)]
100    struct NoResponse {}
101
102    #[derive(Debug, PartialEq, AtatEnum)]
103    enum SimpleEnum {
104        #[at_arg(default, value = 0)]
105        A,
106        #[at_arg(value = 1)]
107        B,
108        #[at_arg(value = 2)]
109        C,
110        #[at_arg(value = 3)]
111        D,
112    }
113    #[derive(Debug, PartialEq, AtatEnum)]
114    #[at_enum(u32)]
115    enum SimpleEnumU32 {
116        #[at_arg(default)]
117        A,
118        B,
119        C,
120        D,
121    }
122
123    #[derive(Debug, PartialEq, AtatEnum)]
124    enum MixedEnum<'a> {
125        #[at_arg(value = 0)]
126        UnitVariant,
127        #[at_arg(value = 1)]
128        SingleSimpleTuple(u8),
129        #[at_arg(default, value = 2)]
130        AdvancedTuple(u8, String<10>, i64, SimpleEnumU32),
131        #[at_arg(value = 3)]
132        SingleSimpleStruct { x: u8 },
133        #[at_arg(value = 4)]
134        AdvancedStruct {
135            a: u8,
136            b: String<10>,
137            c: i64,
138            d: SimpleEnum,
139        },
140        #[at_arg(value = 6)]
141        SingleSimpleTupleLifetime(#[at_arg(len = 10)] &'a str),
142    }
143
144    #[derive(Debug, PartialEq, AtatCmd)]
145    #[at_cmd("+CFUN", NoResponse)]
146    struct LengthTester<'a> {
147        x: u8,
148        y: String<128>,
149        #[at_arg(len = 2)]
150        z: u16,
151        #[at_arg(len = 150)]
152        w: &'a str,
153        a: SimpleEnum,
154        b: SimpleEnumU32,
155        #[at_arg(len = 3)]
156        c: SimpleEnumU32,
157        // d: Vec<SimpleEnumU32, 5>,
158    }
159
160    #[test]
161    fn test_atat_len() {
162        assert_eq!(<char as AtatLen>::LEN, 1);
163        assert_eq!(<bool as AtatLen>::LEN, 5);
164        assert_eq!(<isize as AtatLen>::LEN, 19);
165        assert_eq!(<usize as AtatLen>::LEN, 20);
166        assert_eq!(<u8 as AtatLen>::LEN, 3);
167        assert_eq!(<u16 as AtatLen>::LEN, 5);
168        assert_eq!(<u32 as AtatLen>::LEN, 10);
169        assert_eq!(<u64 as AtatLen>::LEN, 20);
170        assert_eq!(<u128 as AtatLen>::LEN, 39);
171        assert_eq!(<i8 as AtatLen>::LEN, 4);
172        assert_eq!(<i16 as AtatLen>::LEN, 6);
173        assert_eq!(<i32 as AtatLen>::LEN, 11);
174        assert_eq!(<i64 as AtatLen>::LEN, 20);
175        assert_eq!(<i128 as AtatLen>::LEN, 40);
176        assert_eq!(<f32 as AtatLen>::LEN, 42);
177        assert_eq!(<f64 as AtatLen>::LEN, 312);
178
179        assert_eq!(<SimpleEnum as AtatLen>::LEN, 3);
180        assert_eq!(<SimpleEnumU32 as AtatLen>::LEN, 10);
181
182        assert_eq!(<HexStr<u8> as AtatLen>::LEN, 10);
183        assert_eq!(<HexStr<u16> as AtatLen>::LEN, 18);
184        assert_eq!(<HexStr<u32> as AtatLen>::LEN, 30);
185        assert_eq!(<HexStr<u64> as AtatLen>::LEN, 66);
186        assert_eq!(<HexStr<u128> as AtatLen>::LEN, 130);
187
188        #[cfg(feature = "hex_str_arrays")]
189        {
190            assert_eq!(<HexStr<[u8; 16]> as AtatLen>::LEN, 130);
191        }
192
193        // (fields) + (n_fields - 1)
194        // (3 + (1 + 128 + 1) + 2 + (1 + 150 + 1) + 3 + 10 + 3 + (10*5)) + 7
195        assert_eq!(
196            <LengthTester<'_> as AtatLen>::LEN,
197            (3 + (1 + 128 + 1) + 2 + (1 + 150 + 1) + 3 + 10 + 3) + 6
198        );
199        assert_eq!(
200            <MixedEnum<'_> as AtatLen>::LEN,
201            (3 + 3 + (1 + 10 + 1) + 20 + 10) + 4
202        );
203    }
204
205    #[test]
206    fn test_length_serialize() {
207        let mut buf = [0; 360];
208        let len = LengthTester {
209            x: 8,
210            y: String::try_from("SomeString").unwrap(),
211            z: 2,
212            w: "whatup",
213            a: SimpleEnum::A,
214            b: SimpleEnumU32::A,
215            c: SimpleEnumU32::B,
216            // d: Vec::new()
217        }
218        .write(&mut buf);
219        assert_eq!(
220            &buf[..len],
221            Vec::<u8, 360>::from_slice(b"AT+CFUN=8,\"SomeString\",2,\"whatup\",0,0,1\r").unwrap()
222        );
223    }
224
225    #[test]
226    fn test_mixed_enum() {
227        assert_not_impl!(MixedEnum, TryFrom<u8>);
228        assert_not_impl!(MixedEnum, TryFrom<u16>);
229        assert_not_impl!(MixedEnum, TryFrom<u32>);
230
231        assert_eq!(SimpleEnum::try_from(3), Ok(SimpleEnum::D));
232        assert_eq!(SimpleEnumU32::try_from(1), Ok(SimpleEnumU32::B));
233        assert_eq!(
234            to_string::<_, 1>(&MixedEnum::UnitVariant, "CMD", SerializeOptions::default()).unwrap(),
235            String::<1>::try_from("0").unwrap()
236        );
237        assert_eq!(
238            to_string::<_, 10>(
239                &MixedEnum::SingleSimpleTuple(15),
240                "CMD",
241                SerializeOptions::default()
242            )
243            .unwrap(),
244            String::<10>::try_from("1,15").unwrap()
245        );
246        assert_eq!(
247            to_string::<_, 50>(
248                &MixedEnum::AdvancedTuple(
249                    25,
250                    String::try_from("testing").unwrap(),
251                    -54,
252                    SimpleEnumU32::A
253                ),
254                "CMD",
255                SerializeOptions::default()
256            )
257            .unwrap(),
258            String::<50>::try_from("2,25,\"testing\",-54,0").unwrap()
259        );
260        assert_eq!(
261            to_string::<_, 10>(
262                &MixedEnum::SingleSimpleStruct { x: 35 },
263                "CMD",
264                SerializeOptions::default()
265            )
266            .unwrap(),
267            String::<10>::try_from("3,35").unwrap()
268        );
269
270        assert_eq!(
271            to_string::<_, 50>(
272                &MixedEnum::AdvancedStruct {
273                    a: 77,
274                    b: String::try_from("whaat").unwrap(),
275                    c: 88,
276                    d: SimpleEnum::B
277                },
278                "CMD",
279                SerializeOptions::default()
280            )
281            .unwrap(),
282            String::<50>::try_from("4,77,\"whaat\",88,1").unwrap()
283        );
284
285        assert_eq!(Ok(MixedEnum::UnitVariant), from_str::<MixedEnum<'_>>("0"));
286        assert_eq!(
287            Ok(MixedEnum::SingleSimpleTuple(67)),
288            from_str::<MixedEnum<'_>>("1,67")
289        );
290        assert_eq!(
291            Ok(MixedEnum::AdvancedTuple(
292                251,
293                String::try_from("deser").unwrap(),
294                -43,
295                SimpleEnumU32::C
296            )),
297            from_str::<MixedEnum<'_>>("2,251,\"deser\",-43,2")
298        );
299
300        assert_eq!(
301            Ok(MixedEnum::SingleSimpleTupleLifetime("abc")),
302            from_str::<MixedEnum<'_>>("6,\"abc\"")
303        );
304    }
305
306    fn custom_parse(response: &[u8]) -> Result<CustomResponseParse, atat::Error> {
307        Ok(CustomResponseParse {
308            arg1: core::str::from_utf8(&response[6..])
309                .unwrap()
310                .parse()
311                .unwrap(),
312        })
313    }
314
315    #[derive(Debug, PartialEq, AtatResp)]
316    struct CustomResponseParse {
317        arg1: u8,
318    }
319
320    #[derive(Debug, PartialEq, AtatCmd)]
321    #[at_cmd("+CFUN", CustomResponseParse, parse = custom_parse)]
322    struct RequestWithCustomResponseParse;
323
324    #[test]
325    fn test_custom_parse() {
326        assert_eq!(
327            RequestWithCustomResponseParse.parse(Ok(b"ignore123")),
328            Ok(CustomResponseParse { arg1: 123 })
329        );
330    }
331}