grafton_visca_macros/lib.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2//
3// Copyright (c) 2024 Grafton Machine Shed <team@grafton.ai>
4
5//! Procedural macros for the grafton-visca library
6//!
7//! This crate provides derive macros to simplify common patterns
8//! in VISCA command implementations.
9
10#![forbid(unsafe_code)]
11#![deny(missing_docs)]
12#![doc(html_root_url = "https://docs.rs/grafton-visca/0.5.0")]
13
14use syn::{parse_macro_input, DeriveInput};
15
16use proc_macro::TokenStream;
17
18mod forward_control;
19mod inquiry_command;
20mod parser_templates;
21mod value_macros;
22mod visca_enum;
23
24/// Derive macro for implementing ViscaValue trait for command value types
25///
26/// This macro automatically generates the `ViscaValue` trait implementation
27/// for types that represent VISCA command values, providing methods for
28/// converting to and from byte representations.
29///
30/// # Example
31///
32/// ```rust,ignore
33/// use grafton_visca_macros::ViscaValue;
34///
35/// #[derive(ViscaValue, Debug, Copy, Clone)]
36/// #[visca_value(bytes = 2)]
37/// struct ZoomPosition(u16);
38/// ```
39#[proc_macro_derive(ViscaValue, attributes(visca_value))]
40pub fn derive_visca_value(input: TokenStream) -> TokenStream {
41 value_macros::derive_visca_value(input)
42}
43
44/// Derive macro for generating ViscaInquiry implementations with parser support
45///
46/// This macro eliminates boilerplate by automatically generating the `Command` trait
47/// implementation with `to_bytes()`, `response_kind()`, and `command_category()` methods,
48/// as well as a `From` conversion to the `InquiryCommand` enum.
49/// When parser attributes are provided, it also generates a `parse_response()` method.
50///
51/// # Basic Usage
52///
53/// ```rust,ignore
54/// use grafton_visca_macros::ViscaInquiry;
55///
56/// #[derive(ViscaInquiry, Debug, Copy, Clone)]
57/// #[visca(command = 0x00, response = "Power", inquiry_variant = "Power")]
58/// struct PowerInquiry;
59///
60/// #[derive(ViscaInquiry, Debug, Copy, Clone)]
61/// #[visca(command = 0x47, response = "ZoomPosition", inquiry_variant = "ZoomPosition")]
62/// struct ZoomPositionInquiry;
63///
64/// #[derive(ViscaInquiry, Debug, Copy, Clone)]
65/// #[visca(command = 0x12, sub_command = 0x06, response = "PanTiltPosition", inquiry_variant = "PanTiltPosition")]
66/// struct PanTiltPositionInquiry;
67/// ```
68///
69/// # With Response Parsing
70///
71/// Add parser attributes to automatically generate response parsing:
72///
73/// ```rust,ignore
74/// #[derive(ViscaInquiry, Debug, Copy, Clone)]
75/// #[visca(command = 0x00, response = "Power", inquiry_variant = "Power", parser = "bool")]
76/// struct PowerInquiry;
77///
78/// #[derive(ViscaInquiry, Debug, Copy, Clone)]
79/// #[visca(command = 0x47, response = "ZoomPosition", inquiry_variant = "ZoomPosition", parser = "position")]
80/// struct ZoomPositionInquiry;
81///
82/// #[derive(ViscaInquiry, Debug, Copy, Clone)]
83/// #[visca(command = 0xA1, response = "Luminance", inquiry_variant = "Luminance", parser = "byte")]
84/// struct LuminanceInquiry;
85///
86/// #[derive(ViscaInquiry, Debug, Copy, Clone)]
87/// #[visca(command = 0x44, response = "RedGain", inquiry_variant = "RedGain", parser = "offset", field = "gain", offset = 10)]
88/// struct RedGainInquiry;
89/// ```
90///
91/// # Supported Parser Types
92///
93/// - `"bool"` - Boolean values (0x02 = true, 0x03 = false)
94/// - `"byte"` - Direct byte value
95/// - `"position"` - 4-nibble position value (converts to u16)
96/// - `"nibble"` - Extended nibble encoding
97/// - `"offset"` - Byte value with offset subtraction
98/// - `"flags"` - Bit flags (for image flip)
99/// - `"mode"` - Enum value parsing
100/// - `"pan_tilt"` - Special parser for pan/tilt positions
101///
102/// # Requirements
103///
104/// - The struct must have the `#[visca(...)]` attribute with required fields
105/// - The `response` attribute must reference existing variants in the `ResponseType` enum
106/// - The `inquiry_variant` must reference existing variants in the `InquiryCommand` enum
107/// - The struct should implement `Debug`, `Copy`, and `Clone` for full compatibility
108#[proc_macro_derive(ViscaInquiry, attributes(visca))]
109pub fn derive_visca_inquiry(input: TokenStream) -> TokenStream {
110 let input = parse_macro_input!(input as DeriveInput);
111 TokenStream::from(inquiry_command::derive_visca_inquiry_impl(input))
112}
113
114/// Derive macro for automatic enum/u8 conversions in VISCA protocol
115///
116/// This macro automatically generates `TryFrom<u8>` and `From<Enum> for u8`
117/// implementations for enums with explicit discriminants, eliminating boilerplate
118/// code for VISCA protocol value conversions.
119///
120/// # Requirements
121///
122/// - The enum must have unit variants only (no fields)
123/// - All variants must have explicit discriminant values
124/// - Discriminant values must be unique
125/// - Discriminant values must be valid u8 values (0-255)
126///
127/// # Generated Implementations
128///
129/// The macro generates:
130/// - `TryFrom<u8>` - Converts u8 values to enum variants, returning an error for invalid values
131/// - `From<Enum> for u8` - Converts enum variants to their u8 discriminant values
132/// - `is_valid_discriminant(u8) -> bool` - Const function to check if a value is valid
133///
134/// # Basic Example
135///
136/// ```rust,ignore
137/// use grafton_visca_macros::ViscaEnum;
138///
139/// #[derive(Debug, Copy, Clone, PartialEq, Eq, ViscaEnum)]
140/// pub enum ExposureMode {
141/// Auto = 0x00,
142/// Manual = 0x03,
143/// Shutter = 0x0A,
144/// Iris = 0x0B,
145/// Bright = 0x0D,
146/// }
147///
148/// // The macro generates:
149/// // - impl TryFrom<u8> for ExposureMode { ... }
150/// // - impl From<ExposureMode> for u8 { ... }
151/// // - impl ExposureMode { pub const fn is_valid_discriminant(u8) -> bool { ... } }
152/// ```
153///
154/// # Advanced Attributes
155///
156/// The macro supports optional attributes for customization:
157///
158/// ```rust,ignore
159/// #[derive(Debug, Copy, Clone, PartialEq, Eq, ViscaEnum)]
160/// #[visca_enum(error_type = MyError, exhaustive = false)]
161/// pub enum Mode {
162/// #[visca_enum(name = "Automatic Mode")]
163/// Auto = 0x00,
164///
165/// #[visca_enum(name = "Manual Control")]
166/// Manual = 0x03,
167///
168/// #[visca_enum(skip)]
169/// _Reserved = 0xFF, // Not included in TryFrom<u8>
170/// }
171/// ```
172///
173/// ## Enum-level attributes:
174/// - `error_type` - Custom error type for TryFrom (default: `crate::error::Error`)
175/// - `exhaustive` - Whether to generate exhaustive match (default: true)
176///
177/// ## Variant-level attributes:
178/// - `name` - Custom name to use in error messages
179/// - `skip` - Skip this variant in `TryFrom<u8>` (but include in `From<Enum>`)
180///
181/// # Error Handling
182///
183/// The generated `TryFrom<u8>` implementation returns an error with a descriptive
184/// message listing all valid values when an invalid u8 is provided.
185#[proc_macro_derive(ViscaEnum, attributes(visca_enum))]
186pub fn derive_visca_enum(input: TokenStream) -> TokenStream {
187 let input = parse_macro_input!(input as DeriveInput);
188 TokenStream::from(visca_enum::derive_visca_enum_impl(input))
189}
190
191/// Attribute macro for auto-generating CameraSession forwarding implementations
192///
193/// This macro eliminates boilerplate by automatically generating forwarding
194/// implementations of control traits for `CameraSession`, which simply delegate
195/// to the inner `Camera` instance with proper error handling.
196///
197/// # Usage
198///
199/// Apply this attribute to control trait definitions:
200///
201/// ```rust,ignore
202/// use grafton_visca_macros::delegate_to_session;
203///
204/// #[delegate_to_session]
205/// pub trait ZoomControl {
206/// type Mode: Mode;
207///
208/// fn zoom_stop(&self) -> <Self::Mode as Mode>::Ret<'_, Result<(), Error>>;
209/// fn zoom_tele_std(&self) -> <Self::Mode as Mode>::Ret<'_, Result<(), Error>>;
210/// // ... more methods
211/// }
212/// ```
213///
214/// # Generated Code
215///
216/// The macro generates two implementations:
217///
218/// 1. **Async variant** (when `feature = "mode-async"`):
219/// - Forwards calls from `CameraSession<M, P, Tr, Exec>` to the inner camera
220/// - Preserves the generic Mode type `M`
221///
222/// 2. **Blocking variant** (when `feature != "async"`):
223/// - Forwards calls from `CameraSession<Blocking, P, Tr, ()>` to the inner camera
224/// - Uses the concrete `Blocking` mode type
225///
226/// Each forwarding method:
227/// - Guards access with `.as_ref().expect("Cannot access camera after session is closed")`
228/// - Adds `#[inline]` for optimization
229/// - Adds `#[allow(clippy::expect_used)]` to suppress lints
230/// - Preserves all original method attributes and documentation
231///
232/// # Requirements
233///
234/// - The trait must have a `type Mode: Mode` associated type
235/// - Methods should use `<Self::Mode as Mode>::Ret<'_, T>` for return types
236/// - The trait should be implemented for `Camera` (the actual logic)
237///
238/// # Benefits
239///
240/// - **Eliminates duplication**: No need to manually write forwarding impls
241/// - **Prevents drift**: Changes to trait methods automatically propagate
242/// - **Feature-gate aware**: Handles both async and blocking configurations
243/// - **Zero runtime cost**: Generated code is identical to hand-written forwarding
244#[proc_macro_attribute]
245pub fn delegate_to_session(_attr: TokenStream, input: TokenStream) -> TokenStream {
246 let input = parse_macro_input!(input as syn::ItemTrait);
247 TokenStream::from(forward_control::delegate_to_session_impl(input))
248}