Skip to main content

ace_macros/
lib.rs

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