laminar_derive/lib.rs
1//! Derive macros for `LaminarDB`.
2//!
3//! Provides `#[derive(Record)]`, `#[derive(FromRecordBatch)]`, and
4//! `#[derive(ConnectorConfig)]` to eliminate boilerplate.
5//!
6//! # Example
7//!
8//! ```rust,ignore
9//! use laminar_derive::{Record, FromRecordBatch, ConnectorConfig};
10//!
11//! #[derive(Record)]
12//! struct Trade {
13//! symbol: String,
14//! price: f64,
15//! #[event_time]
16//! timestamp: i64,
17//! }
18//!
19//! #[derive(FromRecordBatch)]
20//! struct OhlcBar {
21//! symbol: String,
22//! open: f64,
23//! high: f64,
24//! low: f64,
25//! close: f64,
26//! }
27//!
28//! #[derive(ConnectorConfig)]
29//! struct MySourceConfig {
30//! #[config(key = "bootstrap.servers", required)]
31//! bootstrap_servers: String,
32//!
33//! #[config(key = "batch.size", default = "1000")]
34//! batch_size: usize,
35//!
36//! #[config(key = "timeout.ms", default = "30000", duration_ms)]
37//! timeout: std::time::Duration,
38//!
39//! #[config(key = "api.key", env = "MY_API_KEY")]
40//! api_key: Option<String>,
41//! }
42//! ```
43
44#![allow(clippy::disallowed_types)]
45
46extern crate proc_macro;
47
48use proc_macro::TokenStream;
49
50use syn::{parse_macro_input, DeriveInput};
51
52mod connector_config;
53mod from_record_batch;
54mod record;
55
56/// Derive the `Record` trait for a struct.
57///
58/// Generates `Record::schema()`, `Record::to_record_batch()`, and
59/// `Record::event_time()` implementations automatically.
60///
61/// # Supported Field Types
62///
63/// | Rust Type | Arrow `DataType` |
64/// |-----------|-----------------|
65/// | `bool` | `Boolean` |
66/// | `i8` | `Int8` |
67/// | `i16` | `Int16` |
68/// | `i32` | `Int32` |
69/// | `i64` | `Int64` |
70/// | `u8` | `UInt8` |
71/// | `u16` | `UInt16` |
72/// | `u32` | `UInt32` |
73/// | `u64` | `UInt64` |
74/// | `f32` | `Float32` |
75/// | `f64` | `Float64` |
76/// | `String` | `Utf8` |
77/// | `Vec<u8>` | `Binary` |
78/// | `Option<T>` | nullable variant of `T` |
79///
80/// # Attributes
81///
82/// - `#[event_time]` — marks a field as the event time column
83/// - `#[column("name")]` — overrides the Arrow column name
84/// - `#[nullable]` — marks a non-Option field as nullable in the schema
85#[proc_macro_derive(Record, attributes(event_time, column, nullable))]
86pub fn derive_record(input: TokenStream) -> TokenStream {
87 let input = parse_macro_input!(input as DeriveInput);
88 record::expand_record(input)
89 .unwrap_or_else(|e| e.to_compile_error())
90 .into()
91}
92
93/// Derive the `FromRecordBatch` trait for a struct.
94///
95/// Generates code to deserialize Arrow `RecordBatch` rows into typed structs.
96///
97/// Fields are matched by name (or `#[column("name")]` override). Type
98/// mismatches produce a runtime error.
99///
100/// # Attributes
101///
102/// - `#[column("name")]` — maps the field to a different Arrow column name
103#[proc_macro_derive(FromRecordBatch, attributes(column))]
104pub fn derive_from_record_batch(input: TokenStream) -> TokenStream {
105 let input = parse_macro_input!(input as DeriveInput);
106 from_record_batch::expand_from_record_batch(input)
107 .unwrap_or_else(|e| e.to_compile_error())
108 .into()
109}
110
111/// Derive `FromRow` for a struct.
112///
113/// Like `FromRecordBatch`, generates inherent `from_batch` and
114/// `from_batch_all` methods, and also implements `laminar_db::FromBatch`.
115#[proc_macro_derive(FromRow, attributes(column))]
116pub fn derive_from_row(input: TokenStream) -> TokenStream {
117 let input = parse_macro_input!(input as DeriveInput);
118 from_record_batch::expand_from_row(input)
119 .unwrap_or_else(|e| e.to_compile_error())
120 .into()
121}
122
123/// Derive configuration parsing for connector config structs.
124///
125/// Generates `from_config()`, `validate()`, and `config_keys()` methods
126/// to eliminate boilerplate in connector configuration parsing.
127///
128/// # Attributes
129///
130/// - `#[config(key = "...")]` — specifies the config key name
131/// - `#[config(required)]` — marks the field as required
132/// - `#[config(default = "...")]` — provides a default value
133/// - `#[config(env = "...")]` — reads from environment variable as fallback
134/// - `#[config(description = "...")]` — documentation for the key
135/// - `#[config(duration_ms)]` — parses the value as `Duration` from milliseconds
136///
137/// # Example
138///
139/// ```rust,ignore
140/// #[derive(ConnectorConfig)]
141/// struct MyConfig {
142/// #[config(key = "bootstrap.servers", required, description = "Kafka brokers")]
143/// bootstrap_servers: String,
144///
145/// #[config(key = "batch.size", default = "1000")]
146/// batch_size: usize,
147///
148/// #[config(key = "timeout.ms", default = "30000", duration_ms)]
149/// timeout: std::time::Duration,
150///
151/// #[config(key = "api.key", env = "MY_API_KEY")]
152/// api_key: Option<String>,
153/// }
154///
155/// // Generated methods:
156/// // - MyConfig::from_config(&ConnectorConfig) -> Result<Self, ConnectorError>
157/// // - MyConfig::validate(&self) -> Result<(), ConnectorError>
158/// // - MyConfig::config_keys() -> Vec<ConfigKeySpec>
159/// ```
160#[proc_macro_derive(ConnectorConfig, attributes(config))]
161pub fn derive_connector_config(input: TokenStream) -> TokenStream {
162 let input = parse_macro_input!(input as DeriveInput);
163 connector_config::expand_connector_config(input)
164 .unwrap_or_else(|e| e.to_compile_error())
165 .into()
166}