Skip to main content

ace_macros/
lib.rs

1mod attrs;
2mod read;
3mod repr;
4mod util;
5mod write;
6
7use proc_macro::TokenStream;
8use syn::{parse_macro_input, Data, DeriveInput};
9
10/// Derives `ace_uds::codec::FrameRead` for a struct or enum.
11///
12/// # Required container attribute
13///
14/// ```rust
15/// #[frame(error = "UdsError")]
16/// ```
17///
18/// The error type must implement `Into<DiagError>`.
19///
20/// # Struct field attributes
21///
22/// ## `#[frame(length = "expr")]`
23///
24/// Reads exactly `expr` bytes into a `&'a [u8]` field via `take_n`.
25/// The expression is emitted verbatim and must evaluate to `usize`.
26/// May reference any field declared before this one by name.
27///
28/// ```ignore
29/// #[derive(FrameRead)]
30/// #[frame(error = "UdsError")]
31/// pub struct ReadMemoryByAddressRequest<'a> {
32///     pub address_and_length_format_identifier: u8,
33///     #[frame(length = "(address_and_length_format_identifier & 0x0F) as usize")]
34///     pub memory_address: &'a [u8],
35///     #[frame(length = "(address_and_length_format_identifier >> 4) as usize")]
36///     pub memory_size: &'a [u8],
37/// }
38/// ```
39///
40/// ## `#[frame(read_all)]`
41///
42/// Consumes all remaining bytes into this field. Must be the last field.
43/// Valid for `&'a [u8]` and `Option<&'a [u8]>`.
44/// Mutually exclusive with `length`.
45///
46/// ```ignore
47/// #[derive(FrameRead)]
48/// #[frame(error = "UdsError")]
49/// pub struct ControlDTCSettingRequest<'a> {
50///     pub dtc_setting_type: DTCSettingType,
51///     #[frame(read_all)]
52///     pub dtc_setting_control_option_record: Option<&'a [u8]>,
53/// }
54/// ```
55///
56/// ## `#[frame(skip)]`
57///
58/// Skips this field - receives `Default::default()`, cursor not advanced.
59/// Cannot be combined with `read_all` or `length`.
60///
61/// # Field types with no attribute needed
62///
63/// The following types implement `FrameRead` and are decoded automatically
64/// without any `#[frame(...)]` attribute:
65///
66/// - `u8`, `u16`, `u32` - big-endian
67/// - `[u8; N]` - fixed-size array
68/// - `&'a [u8]` - consumes all remaining bytes (trailing field only)
69/// - `Option<T: FrameRead>` - `None` if empty, `Some` otherwise
70/// - `FrameIter<'a, T: FrameRead>` - lazy iterator over remaining bytes
71/// - Any type implementing `FrameRead`
72///
73/// # Enum variant attributes
74///
75/// ## `#[frame(id = "0x01")]`
76///
77/// Exact discriminant byte. Reads one byte, matches this value, then
78/// decodes the inner type from remaining bytes. Valid for newtype and
79/// unit variants.
80///
81/// ## `#[frame(id_pat = "0x80..=0xFF")]`
82///
83/// Range or catch-all pattern. Variant must be a `u8` newtype - the raw
84/// discriminant byte is passed directly as the inner value.
85///
86/// ```ignore
87/// #[derive(FrameRead)]
88/// #[frame(error = "UdsError")]
89/// pub enum DTCSettingType {
90///     #[frame(id = "0x01")] On,
91///     #[frame(id = "0x02")] Off,
92///     #[frame(id_pat = "0x03..=0xFF")] Reserved(u8),
93/// }
94/// ```
95#[proc_macro_derive(FrameRead, attributes(frame))]
96pub fn frame_read(input: TokenStream) -> TokenStream {
97    let input = parse_macro_input!(input as DeriveInput);
98    read::derive(input).into()
99}
100
101/// Derives `ace_core::codec::FrameWrite` for a struct or enum.
102///
103/// Generates both encode paths gated by the `alloc` feature:
104/// - `#[cfg(not(feature = "alloc"))]` - writes into `&mut &mut [u8]` cursor
105/// - `#[cfg(feature = "alloc")]` - appends into `bytes::BytesMut`
106///
107/// All non-skipped fields are encoded in declaration order by delegating
108/// to each field type's `FrameWrite` impl. `#[frame(length = "...")]` and
109/// `#[frame(read_all)]` are decode-only hints and have no effect on encode.
110///
111/// Supports the same container, field, and variant attributes as `FrameRead`.
112#[proc_macro_derive(FrameWrite, attributes(frame))]
113pub fn frame_write(input: TokenStream) -> TokenStream {
114    let input = parse_macro_input!(input as DeriveInput);
115    write::derive(input).into()
116}
117
118/// Derives both `FrameRead` and `FrameWrite`.
119///
120/// Convenience derive equivalent to `#[derive(FrameRead, FrameWrite)]`.
121/// Use when a type participates in both decode and encode paths.
122///
123/// ```ignore
124/// #[derive(FrameCodec)]
125/// #[frame(error = "UdsError")]
126/// pub struct DiagnosticSessionControlRequest {
127///     pub session_type: SessionType,
128/// }
129/// ```
130#[proc_macro_derive(FrameCodec, attributes(frame))]
131pub fn frame_codec(input: TokenStream) -> TokenStream {
132    let input = parse_macro_input!(input as DeriveInput);
133    let read = read::derive(input.clone());
134    let write = write::derive(input.clone());
135    let repr_conversions = match &input.data {
136        Data::Enum(_) => repr::derive(input),
137        _ => quote::quote! {},
138    };
139    quote::quote! { #read #write #repr_conversions }.into()
140}
141
142#[test]
143fn compile_fail() {
144    let t = trybuild::TestCases::new();
145    t.compile_fail("tests/compile_fail/*.rs")
146}
147
148#[test]
149fn compile_pass() {
150    let t = trybuild::TestCases::new();
151    t.pass("tests/compile_pass/*.rs")
152}