Skip to main content

ace_macros/
lib.rs

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