diff --git a/atat/src/digest.rs b/atat/src/digest.rs index 1dd99b1..4edbbfb 100644 --- a/atat/src/digest.rs +++ b/atat/src/digest.rs @@ -10,6 +10,7 @@ pub enum DigestResult<'a> { None, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ParseError { Incomplete, NoMatch, @@ -351,7 +352,7 @@ pub mod parser { recognize(nom::bytes::complete::take_until("\r\n"))(buf) } - fn take_until_including>( + pub fn take_until_including>( tag: T, ) -> impl Fn(Input) -> IResult where diff --git a/atat/src/ingress.rs b/atat/src/ingress.rs index 900ab63..9fbd591 100644 --- a/atat/src/ingress.rs +++ b/atat/src/ingress.rs @@ -302,8 +302,8 @@ impl< #[cfg(test)] mod tests { use crate::{ - self as atat, atat_derive::AtatUrc, response_slot::ResponseSlot, AtDigester, Response, - UrcChannel, + self as atat, atat_derive::AtatUrc, digest::parser::take_until_including, + response_slot::ResponseSlot, AtDigester, Response, UrcChannel, }; use super::*; @@ -314,6 +314,115 @@ mod tests { ConnectOk, #[at_urc(b"CONNECT FAIL")] ConnectFail, + #[at_urc(b"CUSTOM", parse = custom_parse_fn)] + CustomParse, + + #[at_urc(b"+CREG", parse = custom_cxreg_parse)] + Creg, + } + + /// Example custom parse function, that validates the number of arguments in + /// the URC. + /// + /// NOTE: This will not work correctly with arguments containing quoted + /// commas, like e.g. HTTP responses or arbitrary data URCs. + fn custom_parse_fn<'a, T, Error: nom::error::ParseError<&'a [u8]> + core::fmt::Debug>( + token: T, + ) -> impl Fn(&'a [u8]) -> nom::IResult<&'a [u8], (&'a [u8], usize), Error> + where + &'a [u8]: nom::Compare + nom::FindSubstring, + T: nom::InputLength + Clone + nom::InputTake + nom::InputIter, + { + const N_ARGS: usize = 3; + + move |i| { + let (i, (urc, len)) = atat::digest::parser::urc_helper(token.clone())(i)?; + + let index = urc.iter().position(|&x| x == b':').unwrap_or(urc.len()); + + let (_, cnt) = nom::multi::many0_count(nom::sequence::tuple(( + nom::character::complete::space0::<_, Error>, + take_until_including(","), + )))(&urc[index + 1..])?; + + if cnt + 1 != N_ARGS { + return Err(nom::Err::Error(Error::from_error_kind( + urc, + nom::error::ErrorKind::Count, + ))); + } + + Ok((i, (urc, len))) + } + } + + /// Example custom parse function, that validates the number of arguments in + /// the URC. + /// + /// "+CxREG?" response will always have atleast 2 arguments, both being + /// integers. + /// + /// "+CxREG:" URC will always have at least 1 integer argument, and the + /// second argument, if present, will be a string. + fn custom_cxreg_parse<'a, T, Error: nom::error::ParseError<&'a [u8]> + core::fmt::Debug>( + token: T, + ) -> impl Fn(&'a [u8]) -> nom::IResult<&'a [u8], (&'a [u8], usize), Error> + where + &'a [u8]: nom::Compare + nom::FindSubstring, + T: nom::InputLength + Clone + nom::InputTake + nom::InputIter + nom::AsBytes, + { + move |i| { + let (i, (urc, len)) = atat::digest::parser::urc_helper(token.clone())(i)?; + + let index = urc.iter().position(|&x| x == b':').unwrap_or(urc.len()); + let arguments = &urc[index + 1..]; + + // Parse the first argument + let (rem, _) = nom::sequence::tuple(( + nom::character::complete::space0, + nom::number::complete::u8, + nom::branch::alt((nom::combinator::eof, nom::bytes::complete::tag(","))), + ))(arguments)?; + + if !rem.is_empty() { + // If we have more arguments, we want to make sure this is a quoted string for the URC case. + nom::sequence::tuple(( + nom::character::complete::space0, + nom::sequence::delimited( + nom::bytes::complete::tag("\""), + nom::bytes::complete::escaped( + nom::character::streaming::none_of("\"\\"), + '\\', + nom::character::complete::one_of("\"\\"), + ), + nom::bytes::complete::tag("\""), + ), + nom::branch::alt((nom::combinator::eof, nom::bytes::complete::tag(","))), + ))(rem)?; + } + + Ok((i, (urc, len))) + } + } + + #[test] + fn test_custom_parse_cxreg() { + let creg_resp = b"\r\n+CREG: 2,5,\"9E9A\",\"019624BD\",2\r\n"; + let creg_urc_min = b"\r\n+CREG: 0\r\n"; + let creg_urc_full = b"\r\n+CREG: 5,\"9E9A\",\"0196BDB0\",2\r\n"; + + assert!( + custom_cxreg_parse::<&[u8], nom::error::Error<&[u8]>>(&b"+CREG"[..])(creg_resp) + .is_err() + ); + assert!( + custom_cxreg_parse::<&[u8], nom::error::Error<&[u8]>>(&b"+CREG"[..])(creg_urc_min) + .is_ok() + ); + assert!( + custom_cxreg_parse::<&[u8], nom::error::Error<&[u8]>>(&b"+CREG"[..])(creg_urc_full) + .is_ok() + ); } #[test] @@ -328,12 +437,13 @@ mod tests { let mut sub = urc_channel.subscribe().unwrap(); let buf = ingress.write_buf(); - let data = b"\r\nCONNECT OK\r\n\r\nCONNECT FAIL\r\n\r\nOK\r\n"; + let data = b"\r\nCONNECT OK\r\n\r\nCONNECT FAIL\r\n\r\nCUSTOM: 1,5, true\r\n\r\nOK\r\n"; buf[..data.len()].copy_from_slice(data); ingress.try_advance(data.len()).unwrap(); assert_eq!(Urc::ConnectOk, sub.try_next_message_pure().unwrap()); assert_eq!(Urc::ConnectFail, sub.try_next_message_pure().unwrap()); + assert_eq!(Urc::CustomParse, sub.try_next_message_pure().unwrap()); let response = res_slot.try_get().unwrap(); let response: &Response<100> = &response.borrow(); diff --git a/atat_derive/src/lib.rs b/atat_derive/src/lib.rs index 54d6ed3..5fed3f0 100644 --- a/atat_derive/src/lib.rs +++ b/atat_derive/src/lib.rs @@ -60,6 +60,18 @@ pub fn derive_atat_resp(input: TokenStream) -> TokenStream { /// Automatically derive [`atat::AtatUrc`] trait /// /// [`atat::AtatUrc`]: ../atat/trait.AtatUrc.html +/// +/// ### Field attribute (`#[at_urc(..)]`) +/// The `AtatUrc` derive macro comes with a required field attribute +/// `#[at_urc(..)]`, that is used to specify the URC token to match for. +/// +/// The first argument is required, and must be either a string or a byte +/// literal, specifying the URC token to match for. +/// +/// Allowed optionals for `at_urc` are: +/// - `parse`: **function** Function that should be used to parse for the URC +/// instead of using default `atat::digest::parser::urc_helper` function. The +/// passed functions needs to have a valid non signature. #[proc_macro_derive(AtatUrc, attributes(at_urc))] pub fn derive_atat_urc(input: TokenStream) -> TokenStream { urc::atat_urc(input) @@ -77,10 +89,11 @@ pub fn derive_atat_urc(input: TokenStream) -> TokenStream { /// Furthermore it automatically implements [`atat::AtatLen`], based on the data /// type given in the container attribute. /// -/// **NOTE**: When using this derive macro with struct or tuple variants in the enum, one -/// should take extra care to avoid large size variations of the variants, as the -/// resulting `AtatLen` of the enum will be the length of the representation -/// (see `#[at_enum(..)]`) together with the largest sum of field values in the variant. +/// **NOTE**: When using this derive macro with struct or tuple variants in the +/// enum, one should take extra care to avoid large size variations of the +/// variants, as the resulting `AtatLen` of the enum will be the length of the +/// representation (see `#[at_enum(..)]`) together with the largest sum of field +/// values in the variant. /// /// Eg. /// ```ignore @@ -102,8 +115,8 @@ pub fn derive_atat_urc(input: TokenStream) -> TokenStream { /// `LargeSizeVariations::VariantOne` /// /// ### Container attribute (`#[at_enum(..)]`) -/// The `AtatEnum` derive macro comes with an option of annotating the struct with -/// a container attribute `#[at_enum(..)]`. +/// The `AtatEnum` derive macro comes with an option of annotating the struct +/// with a container attribute `#[at_enum(..)]`. /// /// The container attribute only allows specifying a single parameter, that is /// non-optional if the container attribute is present. The parameter allows @@ -116,10 +129,10 @@ pub fn derive_atat_urc(input: TokenStream) -> TokenStream { /// /// ### Field attribute (`#[at_arg(..)]`) /// The `AtatEnum` derive macro comes with an optional field attribute -/// `#[at_arg(..)]`, that can be specified o some or all of the fields. +/// `#[at_arg(..)]`, that can be specified for some or all of the fields. /// /// Allowed options for `at_arg` are: -/// - `value` **integer** The value of the serialized field +/// - `value`: **integer** The value of the serialized field #[proc_macro_derive(AtatEnum, attributes(at_enum, at_arg))] pub fn derive_atat_enum(input: TokenStream) -> TokenStream { enum_::atat_enum(input) @@ -153,10 +166,12 @@ pub fn derive_atat_enum(input: TokenStream) -> TokenStream { /// 'AT'). Can also be set to '' (empty). /// - `termination`: **string** Overwrite the line termination of the command /// (default '\r\n'). Can also be set to '' (empty). -/// - `quote_escape_strings`: **bool** Whether to escape strings in commands (default true). -/// - `parse`: **function** Function that should be used to parse the response instead of using -/// default `atat::serde_at::from_slice` function. The passed functions needs to have a signature -/// `Result` where `Response` is the type of the response passed in the `at_cmd` +/// - `quote_escape_strings`: **bool** Whether to escape strings in commands +/// (default true). +/// - `parse`: **function** Function that should be used to parse the response +/// instead of using default `atat::serde_at::from_slice` function. The +/// passed functions needs to have a signature `Result` where +/// `Response` is the type of the response passed in the `at_cmd` /// /// ### Field attribute (`#[at_arg(..)]`) /// The `AtatCmd` derive macro comes with an optional field attribute diff --git a/atat_derive/src/parse.rs b/atat_derive/src/parse.rs index 29a47c0..197dcc7 100644 --- a/atat_derive/src/parse.rs +++ b/atat_derive/src/parse.rs @@ -42,6 +42,7 @@ pub struct ArgAttributes { #[derive(Clone)] pub struct UrcAttributes { pub code: LitByteStr, + pub parse: Option, } /// Parsed attributes of `#[at_enum(..)]` @@ -78,8 +79,7 @@ pub fn parse_field_attr(attributes: &[Attribute]) -> Result { for attr in attributes { if attr.path().is_ident("at_arg") { attrs.at_arg = Some(attr.parse_args()?); - } - if attr.path().is_ident("at_urc") { + } else if attr.path().is_ident("at_urc") { attrs.at_urc = Some(attr.parse_args()?); } } @@ -227,9 +227,9 @@ impl Parse for ArgAttributes { impl Parse for UrcAttributes { fn parse(input: ParseStream) -> Result { - let code = match input.parse::()? { - Lit::ByteStr(b) => b, - Lit::Str(s) => LitByteStr::new(s.value().as_bytes(), input.span()), + let code = match input.parse::() { + Ok(Lit::ByteStr(b)) => b, + Ok(Lit::Str(s)) => LitByteStr::new(s.value().as_bytes(), input.span()), _ => { return Err(Error::new( input.span(), @@ -238,13 +238,26 @@ impl Parse for UrcAttributes { } }; - Ok(Self { code }) + let mut at_urc = Self { code, parse: None }; + + while input.parse::().is_ok() { + let optional = input.parse::()?; + if optional.path.is_ident("parse") { + match optional.value { + Expr::Path(ExprPath { path, .. }) => { + at_urc.parse = Some(path); + } + _ => return Err(Error::new(input.span(), "expected function for 'parse'")), + } + } + } + + Ok(at_urc) } } impl Parse for CmdAttributes { fn parse(input: ParseStream) -> Result { - let call_site = Span::call_site(); let cmd = input.parse::()?; let _comma = input.parse::()?; let response_ident = input.parse::()?; @@ -274,7 +287,7 @@ impl Parse for CmdAttributes { } _ => { return Err(Error::new( - call_site, + Span::call_site(), "expected integer value for 'timeout_ms'", )) } @@ -288,7 +301,7 @@ impl Parse for CmdAttributes { } _ => { return Err(Error::new( - call_site, + Span::call_site(), "expected integer value for 'attempts'", )) } @@ -298,7 +311,12 @@ impl Parse for CmdAttributes { Expr::Path(ExprPath { path, .. }) => { at_cmd.parse = Some(path); } - _ => return Err(Error::new(call_site, "expected function for 'parse'")), + _ => { + return Err(Error::new( + Span::call_site(), + "expected function for 'parse'", + )) + } } } else if optional.path.is_ident("reattempt_on_parse_err") { match optional.value { @@ -309,7 +327,7 @@ impl Parse for CmdAttributes { } _ => { return Err(Error::new( - call_site, + Span::call_site(), "expected bool value for 'reattempt_on_parse_err'", )) } @@ -321,7 +339,12 @@ impl Parse for CmdAttributes { }) => { at_cmd.abortable = Some(v.value); } - _ => return Err(Error::new(call_site, "expected bool value for 'abortable'")), + _ => { + return Err(Error::new( + Span::call_site(), + "expected bool value for 'abortable'", + )) + } } } else if optional.path.is_ident("value_sep") { match optional.value { @@ -330,7 +353,12 @@ impl Parse for CmdAttributes { }) => { at_cmd.value_sep = v.value; } - _ => return Err(Error::new(call_site, "expected bool value for 'value_sep'")), + _ => { + return Err(Error::new( + Span::call_site(), + "expected bool value for 'value_sep'", + )) + } } } else if optional.path.is_ident("cmd_prefix") { match optional.value { @@ -341,7 +369,7 @@ impl Parse for CmdAttributes { } _ => { return Err(Error::new( - call_site, + Span::call_site(), "expected string value for 'cmd_prefix'", )) } @@ -355,7 +383,7 @@ impl Parse for CmdAttributes { } _ => { return Err(Error::new( - call_site, + Span::call_site(), "expected string value for 'termination'", )) } @@ -369,7 +397,7 @@ impl Parse for CmdAttributes { } _ => { return Err(Error::new( - call_site, + Span::call_site(), "expected bool value for 'quote_escape_strings'", )) } diff --git a/atat_derive/src/urc.rs b/atat_derive/src/urc.rs index 0bcc45c..a3a859d 100644 --- a/atat_derive/src/urc.rs +++ b/atat_derive/src/urc.rs @@ -19,7 +19,8 @@ pub fn atat_urc(input: TokenStream) -> TokenStream { let (match_arms, digest_arms): (Vec<_>, Vec<_>) = variants.iter().map(|variant| { let UrcAttributes { - code + code, + parse } = variant.attrs.at_urc.clone().unwrap_or_else(|| { panic!( "missing #[at_urc(...)] attribute", @@ -49,8 +50,14 @@ pub fn atat_urc(input: TokenStream) -> TokenStream { } }; - let digest_arm = quote! { - atat::digest::parser::urc_helper(&#code[..]), + let digest_arm = if let Some(parse_fn) = parse { + quote! { + #parse_fn(&#code[..]), + } + } else { + quote! { + atat::digest::parser::urc_helper(&#code[..]), + } }; (parse_arm, digest_arm) diff --git a/serde_at/src/de/mod.rs b/serde_at/src/de/mod.rs index af44614..8cba13c 100644 --- a/serde_at/src/de/mod.rs +++ b/serde_at/src/de/mod.rs @@ -778,6 +778,14 @@ mod tests { p3: Option, } + #[derive(Debug, Deserialize, PartialEq)] + struct OptionEmpty { + p1: u8, + p2: i16, + p3: Option, + p4: bool, + } + #[derive(Clone, Debug, Deserialize, PartialEq)] pub struct CCID { pub ccid: u128, @@ -812,6 +820,16 @@ mod tests { }) ); + assert_eq!( + crate::from_str("+CFG: 2,56,,true"), + Ok(OptionEmpty { + p1: 2, + p2: 56, + p3: None, + p4: true + }) + ); + assert_eq!( crate::from_str("+CFG: 2,56, true"), Ok(CFGOption {