bomboni_request_derive/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use parse::{
4    derived_map::{self, DerivedMap},
5    parse_resource_name::{self, ParseResourceName},
6};
7use proc_macro::TokenStream;
8
9mod parse;
10
11use syn::{DeriveInput, parse_macro_input};
12
13/// A procedural macro that generates a function that parses a resource name into a tuple of typed segments.
14///
15/// Resource name format is documented in Google's AIP [1].
16/// Ending segments can also be optional [2].
17///
18/// [1]: https://google.aip.dev/122
19/// [2]: https://google.aip.dev/162
20///
21/// # Examples
22///
23/// ```
24/// # use bomboni_request_derive::parse_resource_name;
25/// let name = "users/42/projects/1337";
26/// let parsed = parse_resource_name!({
27///    "users": u32,
28///    "projects": u64,
29/// })(name);
30/// assert_eq!(parsed, Some((42, 1337)));
31/// ```
32#[proc_macro]
33pub fn parse_resource_name(input: TokenStream) -> TokenStream {
34    let options = parse_macro_input!(input as ParseResourceName);
35    parse_resource_name::expand(options)
36        .unwrap_or_else(syn::Error::into_compile_error)
37        .into()
38}
39
40/// Derive macro for creating derived map types.
41#[proc_macro]
42pub fn derived_map(input: TokenStream) -> TokenStream {
43    let options = parse_macro_input!(input as DerivedMap);
44    derived_map::expand(options)
45        .unwrap_or_else(syn::Error::into_compile_error)
46        .into()
47}
48
49/// Derive macro for parsing request types.
50///
51/// This macro generates code for converting between different data representations.
52/// It's commonly used for converting protobuf messages to domain models, but can
53/// handle any type conversion scenario.
54///
55/// # Attributes
56///
57/// ## Struct-level attributes
58///
59/// - `source = "Type"` - Source type to parse from (required)
60/// - `write = bool` - Generate write/conversion code back to source type
61/// - `serialize_as = bool` - Generate Serialize for source type
62/// - `deserialize_as = bool` - Generate Deserialize for source type
63/// - `serde_as = bool` - Generate both Serialize and Deserialize for source type
64/// - `request = {...}` - Mark as request message for error handling
65/// - `tagged_union = {...}` - Create tagged union from oneof field
66/// - `bomboni_crate = "path"` - Custom bomboni crate path
67/// - `bomboni_proto_crate = "path"` - Custom `bomboni_proto` crate path
68/// - `bomboni_request_crate = "path"` - Custom `bomboni_request` crate path
69/// - `serde_crate = "path"` - Custom serde crate path
70///
71/// ## Field-level attributes
72///
73/// - `source = "field"` - Source field name to parse from
74/// - `source_field = bool` - Source field name is same as target
75/// - `skip = bool` - Skip parsing this field
76/// - `keep = bool` - Keep field unchanged
77/// - `keep_primitive = bool` - Keep primitive types unchanged
78/// - `unspecified = bool` - Allow unspecified enum values
79/// - `extract = {...}` - Custom extraction plan
80/// - `wrapper = bool` - Parse protobuf wrapper types
81/// - `oneof = bool` - Parse from oneof field
82/// - `enumeration = bool` - Parse enum from i32
83/// - `regex = "pattern"` - Validate with regex
84/// - `timestamp = bool` - Parse protobuf timestamps
85/// - `try_from = "path"` - Custom `TryFrom` conversion
86/// - `convert = {...}` - Custom conversion functions
87/// - `derive = {...}` - Use derived parsing
88/// - `resource = {...}` - Parse resource fields
89/// - `list_query = {...}` - Parse list query
90/// - `search_query = {...}` - Parse search query
91/// - `field_mask = {...}` - Parse field only if field mask allows it
92///
93/// # Examples
94///
95/// Basic usage:
96///
97/// ```rust,ignore
98/// #[derive(Parse)]
99/// #[parse(source = "proto::User")]
100/// struct User {
101///     #[parse(source = "user_name")]
102///     name: String,
103/// }
104/// ```
105///
106/// With bidirectional conversion:
107///
108/// ```rust,ignore
109/// #[derive(Parse)]
110/// #[parse(source = "proto::User", write = true)]
111/// struct User {
112///     name: String,
113///     email: Option<String>,
114/// }
115/// ```
116///
117/// Complex example with multiple features:
118///
119/// ```rust,ignore
120/// #[derive(Parse)]
121/// #[parse(source = "proto::UserMessage", write = true, serde_as = true)]
122/// struct User {
123///     #[parse(source = "user_id", try_from = "UserId::from_str")]
124///     id: UserId,
125///
126///     #[parse(source = "user_name")]
127///     name: String,
128///
129///     #[parse(source = "email_address", regex = r"[^@]+@[^@]+\.[^@]+")]
130///     email: String,
131///
132///     #[parse(source = "created_timestamp", timestamp)]
133///     created_at: OffsetDateTime,
134///
135///     #[parse(source = "status_code", enumeration)]
136///     status: UserStatus,
137///
138///     #[parse(source = "profile_data", keep)]
139///     metadata: HashMap<String, String>,
140/// }
141/// ```
142///
143/// Field mask example for update operations:
144///
145/// ```rust,ignore
146/// #[derive(Parse)]
147/// #[parse(source = "UpdateBookRequest", write = true)]
148/// struct ParsedUpdateBookRequest {
149///     #[parse(source = "book?.name", convert = book_id_convert)]
150///     id: BookId,
151///     #[parse(source = "book?.display_name", field_mask { field = book, mask = update_mask })]
152///     display_name: Option<String>,
153/// }
154/// ```
155#[proc_macro_derive(Parse, attributes(parse))]
156pub fn derive_parse(input: TokenStream) -> TokenStream {
157    let input = parse_macro_input!(input as DeriveInput);
158    parse::expand(input)
159        .unwrap_or_else(syn::Error::into_compile_error)
160        .into()
161}