1use darling::{FromDeriveInput, FromMeta};
40use proc_macro::TokenStream;
41use quote::quote;
42use syn::{parse_macro_input, DeriveInput};
43
44#[derive(Debug, FromDeriveInput)]
46#[darling(attributes(source), supports(struct_named))]
47struct SourceAttrs {
48 ident: syn::Ident,
49 #[darling(default)]
51 #[allow(dead_code)]
52 name: Option<String>,
53 #[darling(default)]
55 #[allow(dead_code)]
56 version: Option<String>,
57 #[darling(default)]
59 #[allow(dead_code)]
60 description: Option<String>,
61 #[darling(default)]
63 #[allow(dead_code)]
64 author: Option<String>,
65 #[darling(default)]
67 #[allow(dead_code)]
68 license: Option<String>,
69 #[darling(default)]
71 #[allow(dead_code)]
72 documentation_url: Option<String>,
73 #[darling(default)]
75 #[allow(dead_code)]
76 incremental: bool,
77 #[darling(default)]
79 #[allow(dead_code)]
80 source_types: Option<SourceTypesAttr>,
81}
82
83#[derive(Debug, Default, FromMeta)]
84#[allow(dead_code)]
85struct SourceTypesAttr {
86 full_refresh: bool,
87 incremental: bool,
88}
89
90#[derive(Debug, FromDeriveInput)]
92#[darling(attributes(sink), supports(struct_named))]
93struct SinkAttrs {
94 ident: syn::Ident,
95 #[darling(default)]
97 #[allow(dead_code)]
98 name: Option<String>,
99 #[darling(default)]
101 #[allow(dead_code)]
102 version: Option<String>,
103 #[darling(default)]
105 #[allow(dead_code)]
106 description: Option<String>,
107 #[darling(default)]
109 #[allow(dead_code)]
110 author: Option<String>,
111 #[darling(default)]
113 #[allow(dead_code)]
114 license: Option<String>,
115 #[darling(default)]
117 #[allow(dead_code)]
118 documentation_url: Option<String>,
119 #[darling(default)]
121 #[allow(dead_code)]
122 batching: bool,
123 #[darling(default)]
125 #[allow(dead_code)]
126 batch_size: Option<usize>,
127}
128
129#[derive(Debug, FromDeriveInput)]
131#[darling(attributes(transform), supports(struct_named))]
132struct TransformAttrs {
133 ident: syn::Ident,
134 #[darling(default)]
136 #[allow(dead_code)]
137 name: Option<String>,
138 #[darling(default)]
140 #[allow(dead_code)]
141 version: Option<String>,
142 #[darling(default)]
144 #[allow(dead_code)]
145 description: Option<String>,
146}
147
148#[proc_macro_derive(SourceConfig, attributes(source))]
181pub fn derive_source_config(input: TokenStream) -> TokenStream {
182 let input = parse_macro_input!(input as DeriveInput);
183
184 let attrs = match SourceAttrs::from_derive_input(&input) {
185 Ok(v) => v,
186 Err(e) => return TokenStream::from(e.write_errors()),
187 };
188
189 let struct_name = &attrs.ident;
190 let spec_struct_name = quote::format_ident!("{}Spec", struct_name);
191
192 let name = attrs.name.unwrap_or_else(|| {
193 let name = struct_name.to_string();
194 name.strip_suffix("Config").unwrap_or(&name).to_lowercase()
195 });
196 let version_code = match &attrs.version {
200 Some(v) => quote! { #v },
201 None => quote! { match option_env!("CARGO_PKG_VERSION") {
202 Some(v) => v,
203 None => "0.0.1",
204 } },
205 };
206
207 let description_code = match &attrs.description {
208 Some(desc) => quote! { .description(#desc) },
209 None => quote! {},
210 };
211
212 let author_code = match &attrs.author {
213 Some(author) => quote! { .author(#author) },
214 None => quote! {},
215 };
216
217 let license_code = match &attrs.license {
218 Some(license) => quote! { .license(#license) },
219 None => quote! {},
220 };
221
222 let doc_url_code = match &attrs.documentation_url {
223 Some(url) => quote! { .documentation_url(#url) },
224 None => quote! {},
225 };
226
227 let incremental_code = if attrs.incremental {
228 quote! { .incremental(true) }
229 } else {
230 quote! {}
231 };
232
233 let expanded = quote! {
234 pub struct #spec_struct_name;
236
237 impl #spec_struct_name {
238 pub fn spec() -> rivven_connect::ConnectorSpec {
240 rivven_connect::ConnectorSpec::builder(#name, #version_code)
241 #description_code
242 #author_code
243 #license_code
244 #doc_url_code
245 #incremental_code
246 .config_schema::<#struct_name>()
247 .build()
248 }
249
250 pub const fn name() -> &'static str {
252 #name
253 }
254
255 pub fn version() -> &'static str {
257 #version_code
258 }
259 }
260 };
261
262 TokenStream::from(expanded)
263}
264
265#[proc_macro_derive(SinkConfig, attributes(sink))]
301pub fn derive_sink_config(input: TokenStream) -> TokenStream {
302 let input = parse_macro_input!(input as DeriveInput);
303
304 let attrs = match SinkAttrs::from_derive_input(&input) {
305 Ok(v) => v,
306 Err(e) => return TokenStream::from(e.write_errors()),
307 };
308
309 let struct_name = &attrs.ident;
310 let spec_struct_name = quote::format_ident!("{}Spec", struct_name);
311
312 let name = attrs.name.unwrap_or_else(|| {
313 let name = struct_name.to_string();
314 name.strip_suffix("Config").unwrap_or(&name).to_lowercase()
315 });
316 let version_code = match &attrs.version {
317 Some(v) => quote! { #v },
318 None => quote! { match option_env!("CARGO_PKG_VERSION") {
319 Some(v) => v,
320 None => "0.0.1",
321 } },
322 };
323
324 let description_code = match &attrs.description {
325 Some(desc) => quote! { .description(#desc) },
326 None => quote! {},
327 };
328
329 let author_code = match &attrs.author {
330 Some(author) => quote! { .author(#author) },
331 None => quote! {},
332 };
333
334 let license_code = match &attrs.license {
335 Some(license) => quote! { .license(#license) },
336 None => quote! {},
337 };
338
339 let doc_url_code = match &attrs.documentation_url {
340 Some(url) => quote! { .documentation_url(#url) },
341 None => quote! {},
342 };
343
344 let batch_config_code = if attrs.batching {
345 let batch_size = attrs.batch_size.unwrap_or(10_000);
346 quote! {
347 pub fn batch_config() -> rivven_connect::BatchConfig {
349 rivven_connect::BatchConfig {
350 max_records: #batch_size,
351 ..Default::default()
352 }
353 }
354 }
355 } else {
356 quote! {}
357 };
358
359 let expanded = quote! {
360 pub struct #spec_struct_name;
362
363 impl #spec_struct_name {
364 pub fn spec() -> rivven_connect::ConnectorSpec {
366 rivven_connect::ConnectorSpec::builder(#name, #version_code)
367 #description_code
368 #author_code
369 #license_code
370 #doc_url_code
371 .config_schema::<#struct_name>()
372 .build()
373 }
374
375 pub const fn name() -> &'static str {
377 #name
378 }
379
380 pub fn version() -> &'static str {
382 #version_code
383 }
384
385 #batch_config_code
386 }
387 };
388
389 TokenStream::from(expanded)
390}
391
392#[proc_macro_derive(TransformConfig, attributes(transform))]
411pub fn derive_transform_config(input: TokenStream) -> TokenStream {
412 let input = parse_macro_input!(input as DeriveInput);
413
414 let attrs = match TransformAttrs::from_derive_input(&input) {
415 Ok(v) => v,
416 Err(e) => return TokenStream::from(e.write_errors()),
417 };
418
419 let struct_name = &attrs.ident;
420 let spec_struct_name = quote::format_ident!("{}Spec", struct_name);
421
422 let name = attrs.name.unwrap_or_else(|| {
423 let name = struct_name.to_string();
424 name.strip_suffix("Config").unwrap_or(&name).to_lowercase()
425 });
426 let version_code = match &attrs.version {
427 Some(v) => quote! { #v },
428 None => quote! { match option_env!("CARGO_PKG_VERSION") {
429 Some(v) => v,
430 None => "0.0.1",
431 } },
432 };
433
434 let description_code = match attrs.description {
436 Some(desc) => quote! { .description(#desc) },
437 None => quote! {},
438 };
439
440 let expanded = quote! {
441 pub struct #spec_struct_name;
443
444 impl #spec_struct_name {
445 pub fn spec() -> rivven_connect::ConnectorSpec {
447 rivven_connect::ConnectorSpec::builder(#name, #version_code)
448 #description_code
449 .config_schema::<#struct_name>()
450 .build()
451 }
452
453 pub const fn name() -> &'static str {
455 #name
456 }
457
458 pub fn version() -> &'static str {
460 #version_code
461 }
462 }
463 };
464
465 TokenStream::from(expanded)
466}
467
468#[derive(Debug, Default, FromMeta)]
470struct ConnectorSpecAttrs {
471 #[darling(default)]
473 name: Option<String>,
474 #[darling(default)]
476 #[allow(dead_code)]
477 version: Option<String>,
478 #[darling(default)]
480 #[allow(dead_code)]
481 description: Option<String>,
482 #[darling(default)]
484 #[allow(dead_code)]
485 documentation_url: Option<String>,
486}
487
488#[proc_macro_attribute]
507pub fn connector_spec(attr: TokenStream, item: TokenStream) -> TokenStream {
508 let attr_args = match darling::ast::NestedMeta::parse_meta_list(attr.into()) {
510 Ok(v) => v,
511 Err(e) => return TokenStream::from(darling::Error::from(e).write_errors()),
512 };
513
514 let attrs = match ConnectorSpecAttrs::from_list(&attr_args) {
515 Ok(v) => v,
516 Err(e) => return TokenStream::from(e.write_errors()),
517 };
518
519 let name = match attrs.name {
522 Some(n) => n,
523 None => {
524 return TokenStream::from(
525 syn::Error::new(
526 proc_macro2::Span::call_site(),
527 "connector_spec requires `name = \"...\"` attribute",
528 )
529 .to_compile_error(),
530 );
531 }
532 };
533 let version_code = match &attrs.version {
534 Some(v) => quote! { #v },
535 None => quote! { match option_env!("CARGO_PKG_VERSION") {
536 Some(v) => v,
537 None => "0.0.1",
538 } },
539 };
540
541 let description_code = match attrs.description {
543 Some(desc) => quote! { .description(#desc) },
544 None => quote! {},
545 };
546
547 let doc_url_code = match attrs.documentation_url {
548 Some(url) => quote! { .documentation_url(#url) },
549 None => quote! {},
550 };
551
552 let item: proc_macro2::TokenStream = item.into();
553
554 let expanded = quote! {
555 #item
556
557 pub fn connector_spec() -> rivven_connect::ConnectorSpec {
559 rivven_connect::ConnectorSpec::builder(#name, #version_code)
560 #description_code
561 #doc_url_code
562 .build()
563 }
564 };
565
566 TokenStream::from(expanded)
567}
568
569#[cfg(test)]
570mod tests {
571 #[test]
572 fn test_derives_compile() {
573 }
576}