1use heapless::{String, Vec};
2use serde_at::HexStr;
3
4pub 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
42impl_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
69impl<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 }
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 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 }
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}