with_options/
lib.rs

1// Copyright 2025 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use proc_macro::TokenStream;
16use quote::quote;
17use syn::{DeriveInput, parse_macro_input};
18
19/// Annotates that the struct represents the `WITH` properties for a connector.
20/// This implements a marker trait `WithOptions`.
21/// It's also used to generate the `with_option_*.yaml` files.
22///
23/// ## Notes about how to define property structs
24///
25/// ### Prefer strongly-typed fields
26///
27/// Avoid using `HashMap` with `#[serde(flatten)]` to include all unknown fields.
28/// Declare all fields explicitly instead.
29///
30/// The only exception now is CDC, which needs to pass a lot of options as-is to Debezium.
31///
32/// ### Common struct
33///
34/// When there are some fields can be grouped together, and/or can be shared by source and sink,
35/// or by multiple connectors, define a struct for them and use the common struct as a
36/// `#[serde(flatten)]` field.
37///
38/// Add `#[derive(WithOptions)]` to both the outer and the inner struct.
39///
40/// Avoid using nested `#[serde(flatten)]` field in the common struct,
41/// because this will lead to unexpected serde behaviors.
42/// Put all flatten fields in the top-level struct instead.
43///
44/// ## Field Annotations
45///
46/// ### Allow Alter On Fly fields (allowed in `alter table connector with`)
47///
48/// Use `#[with_option(allow_alter_on_fly)]` to mark fields that can be changed on the fly
49/// without requiring a restart or recreation of the connector:
50///
51/// ```ignore
52/// #[derive(WithOptions)]
53/// struct MyConnectorConfig {
54///     #[with_option(allow_alter_on_fly)]
55///     pub rate_limit: Option<u32>,
56///     pub endpoint: String,  // not allow_alter_on_fly
57/// }
58/// ```
59#[proc_macro_derive(WithOptions, attributes(with_option))]
60pub fn derive_helper_attr(input: TokenStream) -> TokenStream {
61    let input = parse_macro_input!(input as DeriveInput);
62
63    let fields = match input.data {
64        syn::Data::Struct(ref data) => match data.fields {
65            syn::Fields::Named(ref fields) => &fields.named,
66            _ => return quote! { compile_error!("WithOptions can only be derived for structs with named fields"); }.into(),
67        },
68        _ => return quote! { compile_error!("WithOptions can only be derived for structs"); }.into(),
69    };
70
71    let mut assert_impls = vec![];
72
73    for field in fields {
74        let field_name = field.ident.as_ref().unwrap();
75
76        assert_impls.push(quote!(
77            crate::with_options::WithOptions::assert_receiver_is_with_options(&self.#field_name);
78        ))
79    }
80
81    let struct_name = input.ident;
82    // This macro is only be expected to used in risingwave_connector. This trait is also defined there.
83    if input.generics.params.is_empty() {
84        quote! {
85            impl crate::with_options::WithOptions for #struct_name {
86                fn assert_receiver_is_with_options(&self) {
87                    #(#assert_impls)*
88                }
89            }
90        }
91        .into()
92    } else {
93        // Note: CDC properties have generics.
94        let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
95        quote! {
96            impl #impl_generics crate::with_options::WithOptions for #struct_name #ty_generics #where_clause {
97                fn assert_receiver_is_with_options(&self) {
98                    #(#assert_impls)*
99                }
100            }
101        }.into()
102    }
103}