1
2use proc_macro::TokenStream;
55use proc_macro2::TokenStream as TokenStream2;
56use quote::quote;
57use syn::{
58 parse_macro_input, Attribute, Data, DeriveInput, Expr, Field, Fields, GenericArgument, Ident,
59 Lit, Meta, PathArguments, Type,
60};
61
62#[proc_macro_derive(Schema, attributes(schema))]
63pub fn derive_schema(input: TokenStream) -> TokenStream {
64 let input = parse_macro_input!(input as DeriveInput);
65
66 match derive_schema_impl(&input) {
67 Ok(tokens) => tokens.into(),
68 Err(err) => err.to_compile_error().into(),
69 }
70}
71
72fn derive_schema_impl(input: &DeriveInput) -> syn::Result<TokenStream2> {
73 let name = &input.ident;
74 let name_str = name.to_string();
75
76 let type_attrs = parse_type_attributes(&input.attrs)?;
77 let schema_name = type_attrs.rename.as_deref().unwrap_or(&name_str);
78
79 let schema_def = match &input.data {
80 Data::Struct(data) => generate_struct_schema(name, &data.fields, &type_attrs)?,
81 Data::Enum(data) => generate_enum_schema(name, data, &type_attrs)?,
82 Data::Union(_) => {
83 return Err(syn::Error::new_spanned(
84 input,
85 "Schema cannot be derived for unions",
86 ))
87 }
88 };
89
90 let expanded = quote! {
91 impl ::omni_schema_core::Schema for #name {
92 fn schema_definition() -> ::omni_schema_core::SchemaDefinition {
93 #schema_def
94 }
95
96 fn schema_name() -> &'static str {
97 #schema_name
98 }
99 }
100 };
101
102 Ok(expanded)
103}
104
105#[derive(Default)]
106struct TypeAttributes {
107 description: Option<String>,
108 rename: Option<String>,
109 rename_all: Option<String>,
110 deprecated: bool,
111 tag: Option<String>,
112 content: Option<String>,
113 untagged: bool,
114}
115
116#[derive(Default)]
117struct FieldAttributes {
118 description: Option<String>,
119 rename: Option<String>,
120 min_length: Option<usize>,
121 max_length: Option<usize>,
122 minimum: Option<f64>,
123 maximum: Option<f64>,
124 pattern: Option<String>,
125 format: Option<String>,
126 default: Option<Expr>,
127 deprecated: bool,
128 skip: bool,
129 flatten: bool,
130 nullable: Option<bool>,
131}
132
133fn parse_type_attributes(attrs: &[Attribute]) -> syn::Result<TypeAttributes> {
134 let mut result = TypeAttributes::default();
135
136 for attr in attrs {
137 if !attr.path().is_ident("schema") {
138 continue;
139 }
140
141 attr.parse_nested_meta(|meta| {
142 if meta.path.is_ident("description") {
143 let value: Lit = meta.value()?.parse()?;
144 if let Lit::Str(s) = value {
145 result.description = Some(s.value());
146 }
147 } else if meta.path.is_ident("rename") {
148 let value: Lit = meta.value()?.parse()?;
149 if let Lit::Str(s) = value {
150 result.rename = Some(s.value());
151 }
152 } else if meta.path.is_ident("rename_all") {
153 let value: Lit = meta.value()?.parse()?;
154 if let Lit::Str(s) = value {
155 result.rename_all = Some(s.value());
156 }
157 } else if meta.path.is_ident("deprecated") {
158 result.deprecated = true;
159 } else if meta.path.is_ident("tag") {
160 let value: Lit = meta.value()?.parse()?;
161 if let Lit::Str(s) = value {
162 result.tag = Some(s.value());
163 }
164 } else if meta.path.is_ident("content") {
165 let value: Lit = meta.value()?.parse()?;
166 if let Lit::Str(s) = value {
167 result.content = Some(s.value());
168 }
169 } else if meta.path.is_ident("untagged") {
170 result.untagged = true;
171 }
172 Ok(())
173 })?;
174 }
175
176 for attr in attrs {
177 if attr.path().is_ident("doc") {
178 if let Meta::NameValue(meta) = &attr.meta {
179 if let Expr::Lit(expr_lit) = &meta.value {
180 if let Lit::Str(s) = &expr_lit.lit {
181 let doc = s.value().trim().to_string();
182 if result.description.is_none() {
183 result.description = Some(doc);
184 } else if let Some(ref mut desc) = result.description {
185 desc.push('\n');
186 desc.push_str(&doc);
187 }
188 }
189 }
190 }
191 }
192 }
193
194 Ok(result)
195}
196
197fn parse_field_attributes(attrs: &[Attribute]) -> syn::Result<FieldAttributes> {
198 let mut result = FieldAttributes::default();
199
200 for attr in attrs {
201 if !attr.path().is_ident("schema") {
202 continue;
203 }
204
205 attr.parse_nested_meta(|meta| {
206 if meta.path.is_ident("description") {
207 let value: Lit = meta.value()?.parse()?;
208 if let Lit::Str(s) = value {
209 result.description = Some(s.value());
210 }
211 } else if meta.path.is_ident("rename") {
212 let value: Lit = meta.value()?.parse()?;
213 if let Lit::Str(s) = value {
214 result.rename = Some(s.value());
215 }
216 } else if meta.path.is_ident("min_length") {
217 let value: Lit = meta.value()?.parse()?;
218 if let Lit::Int(i) = value {
219 result.min_length = Some(i.base10_parse()?);
220 }
221 } else if meta.path.is_ident("max_length") {
222 let value: Lit = meta.value()?.parse()?;
223 if let Lit::Int(i) = value {
224 result.max_length = Some(i.base10_parse()?);
225 }
226 } else if meta.path.is_ident("minimum") {
227 let value: Lit = meta.value()?.parse()?;
228 match value {
229 Lit::Int(i) => result.minimum = Some(i.base10_parse::<i64>()? as f64),
230 Lit::Float(f) => result.minimum = Some(f.base10_parse()?),
231 _ => {}
232 }
233 } else if meta.path.is_ident("maximum") {
234 let value: Lit = meta.value()?.parse()?;
235 match value {
236 Lit::Int(i) => result.maximum = Some(i.base10_parse::<i64>()? as f64),
237 Lit::Float(f) => result.maximum = Some(f.base10_parse()?),
238 _ => {}
239 }
240 } else if meta.path.is_ident("pattern") {
241 let value: Lit = meta.value()?.parse()?;
242 if let Lit::Str(s) = value {
243 result.pattern = Some(s.value());
244 }
245 } else if meta.path.is_ident("format") {
246 let value: Lit = meta.value()?.parse()?;
247 if let Lit::Str(s) = value {
248 result.format = Some(s.value());
249 }
250 } else if meta.path.is_ident("default") {
251 let value: Expr = meta.value()?.parse()?;
252 result.default = Some(value);
253 } else if meta.path.is_ident("deprecated") {
254 result.deprecated = true;
255 } else if meta.path.is_ident("skip") {
256 result.skip = true;
257 } else if meta.path.is_ident("flatten") {
258 result.flatten = true;
259 } else if meta.path.is_ident("nullable") {
260 result.nullable = Some(true);
261 }
262 Ok(())
263 })?;
264 }
265
266 for attr in attrs {
267 if attr.path().is_ident("doc") {
268 if let Meta::NameValue(meta) = &attr.meta {
269 if let Expr::Lit(expr_lit) = &meta.value {
270 if let Lit::Str(s) = &expr_lit.lit {
271 let doc = s.value().trim().to_string();
272 if result.description.is_none() {
273 result.description = Some(doc);
274 } else if let Some(ref mut desc) = result.description {
275 desc.push('\n');
276 desc.push_str(&doc);
277 }
278 }
279 }
280 }
281 }
282 }
283
284 Ok(result)
285}
286
287fn generate_struct_schema(
288 name: &Ident,
289 fields: &Fields,
290 type_attrs: &TypeAttributes,
291) -> syn::Result<TokenStream2> {
292 let name_str = type_attrs.rename.as_deref().unwrap_or(&name.to_string()).to_string();
293
294 match fields {
295 Fields::Named(named) => {
296 let field_defs = generate_named_fields(&named.named, type_attrs)?;
297
298 let description = match &type_attrs.description {
299 Some(desc) => quote! { .with_description(#desc) },
300 None => quote! {},
301 };
302
303 let deprecated = if type_attrs.deprecated {
304 quote! { .deprecated() }
305 } else {
306 quote! {}
307 };
308
309 Ok(quote! {
310 {
311 let mut struct_def = ::omni_schema_core::types::StructDefinition::new();
312 #field_defs
313 ::omni_schema_core::SchemaDefinition::new(
314 #name_str,
315 ::omni_schema_core::types::SchemaType::Struct(struct_def),
316 )
317 #description
318 #deprecated
319 }
320 })
321 }
322 Fields::Unnamed(unnamed) => {
323 let field_defs = generate_tuple_fields(&unnamed.unnamed)?;
324
325 let description = match &type_attrs.description {
326 Some(desc) => quote! { .with_description(#desc) },
327 None => quote! {},
328 };
329
330 let deprecated = if type_attrs.deprecated {
331 quote! { .deprecated() }
332 } else {
333 quote! {}
334 };
335
336 Ok(quote! {
337 {
338 let mut struct_def = ::omni_schema_core::types::StructDefinition::tuple();
339 #field_defs
340 ::omni_schema_core::SchemaDefinition::new(
341 #name_str,
342 ::omni_schema_core::types::SchemaType::Struct(struct_def),
343 )
344 #description
345 #deprecated
346 }
347 })
348 }
349 Fields::Unit => {
350 let description = match &type_attrs.description {
351 Some(desc) => quote! { .with_description(#desc) },
352 None => quote! {},
353 };
354
355 let deprecated = if type_attrs.deprecated {
356 quote! { .deprecated() }
357 } else {
358 quote! {}
359 };
360
361 Ok(quote! {
362 ::omni_schema_core::SchemaDefinition::new(
363 #name_str,
364 ::omni_schema_core::types::SchemaType::Unit,
365 )
366 #description
367 #deprecated
368 })
369 }
370 }
371}
372
373fn generate_named_fields<'a>(
374 fields: impl IntoIterator<Item = &'a Field>,
375 type_attrs: &TypeAttributes,
376) -> syn::Result<TokenStream2> {
377 let mut tokens = TokenStream2::new();
378
379 for field in fields {
380 let field_attrs = parse_field_attributes(&field.attrs)?;
381
382 if field_attrs.skip {
383 continue;
384 }
385
386 let field_name = field.ident.as_ref().unwrap();
387 let field_name_str = field_attrs
388 .rename
389 .as_deref()
390 .map(|s| s.to_string())
391 .unwrap_or_else(|| {
392 apply_rename_rule(&field_name.to_string(), type_attrs.rename_all.as_deref())
393 });
394
395 let original_name_str = field_name.to_string();
396 let ty = &field.ty;
397 let schema_type = generate_type_schema(ty);
398
399 let mut field_builder = quote! {
400 ::omni_schema_core::types::StructField::new(#schema_type, #original_name_str)
401 };
402
403 if let Some(ref desc) = field_attrs.description {
404 field_builder = quote! { #field_builder.with_description(#desc) };
405 }
406
407 if let Some(ref format) = field_attrs.format {
408 field_builder = quote! { #field_builder.with_format(#format) };
409 }
410
411 if let Some(min) = field_attrs.min_length {
412 field_builder = quote! { #field_builder.with_length(Some(#min), None) };
413 }
414
415 if let Some(max) = field_attrs.max_length {
416 if field_attrs.min_length.is_some() {
417 let min = field_attrs.min_length.unwrap();
418 field_builder = quote! {
419 {
420 let mut f = #field_builder;
421 f.min_length = Some(#min);
422 f.max_length = Some(#max);
423 f
424 }
425 };
426 } else {
427 field_builder = quote! { #field_builder.with_length(None, Some(#max)) };
428 }
429 }
430
431 if let Some(min) = field_attrs.minimum {
432 field_builder = quote! { #field_builder.with_range(Some(#min), None) };
433 }
434
435 if let Some(max) = field_attrs.maximum {
436 if field_attrs.minimum.is_some() {
437 let min = field_attrs.minimum.unwrap();
438 field_builder = quote! { #field_builder.with_range(Some(#min), Some(#max)) };
439 } else {
440 field_builder = quote! { #field_builder.with_range(None, Some(#max)) };
441 }
442 }
443
444 if let Some(ref pattern) = field_attrs.pattern {
445 field_builder = quote! { #field_builder.with_pattern(#pattern) };
446 }
447
448 if field_attrs.deprecated {
449 field_builder = quote! { #field_builder.deprecated() };
450 }
451
452 if field_attrs.flatten {
453 field_builder = quote! { #field_builder.flattened() };
454 }
455
456 if let Some(true) = field_attrs.nullable {
457 field_builder = quote! { #field_builder.nullable() };
458 }
459
460 if let Some(ref default_expr) = field_attrs.default {
461 field_builder = quote! {
462 #field_builder.with_default(::serde_json::json!(#default_expr))
463 };
464 }
465
466 tokens.extend(quote! {
467 struct_def = struct_def.with_field(#field_name_str, #field_builder);
468 });
469 }
470
471 Ok(tokens)
472}
473
474fn generate_tuple_fields<'a>(
475 fields: impl IntoIterator<Item = &'a Field>,
476) -> syn::Result<TokenStream2> {
477 let mut tokens = TokenStream2::new();
478
479 for (i, field) in fields.into_iter().enumerate() {
480 let field_attrs = parse_field_attributes(&field.attrs)?;
481 let field_name = format!("{}", i);
482 let ty = &field.ty;
483 let schema_type = generate_type_schema(ty);
484
485 let mut field_builder = quote! {
486 ::omni_schema_core::types::StructField::new(#schema_type, #field_name)
487 };
488
489 if let Some(ref desc) = field_attrs.description {
490 field_builder = quote! { #field_builder.with_description(#desc) };
491 }
492
493 tokens.extend(quote! {
494 struct_def = struct_def.with_field(#field_name, #field_builder);
495 });
496 }
497
498 Ok(tokens)
499}
500
501fn generate_enum_schema(
502 name: &Ident,
503 data: &syn::DataEnum,
504 type_attrs: &TypeAttributes,
505) -> syn::Result<TokenStream2> {
506 let name_str = type_attrs.rename.as_deref().unwrap_or(&name.to_string()).to_string();
507
508 let representation = if type_attrs.untagged {
509 quote! { ::omni_schema_core::types::EnumRepresentation::Untagged }
510 } else if let Some(ref tag) = type_attrs.tag {
511 if let Some(ref content) = type_attrs.content {
512 quote! { ::omni_schema_core::types::EnumRepresentation::Adjacent {
513 tag: #tag.to_string(),
514 content: #content.to_string()
515 }}
516 } else {
517 quote! { ::omni_schema_core::types::EnumRepresentation::Internal {
518 tag: #tag.to_string()
519 }}
520 }
521 } else {
522 quote! { ::omni_schema_core::types::EnumRepresentation::External }
523 };
524
525 let mut variant_tokens = TokenStream2::new();
526
527 for variant in &data.variants {
528 let variant_attrs = parse_field_attributes(&variant.attrs)?;
529 let variant_name = &variant.ident;
530 let variant_name_str = variant_attrs
531 .rename
532 .as_deref()
533 .map(|s| s.to_string())
534 .unwrap_or_else(|| {
535 apply_rename_rule(&variant_name.to_string(), type_attrs.rename_all.as_deref())
536 });
537
538 let variant_def = match &variant.fields {
539 Fields::Unit => {
540 quote! { ::omni_schema_core::types::EnumVariant::unit(#variant_name_str) }
541 }
542 Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
543 let ty = &fields.unnamed.first().unwrap().ty;
544 let schema_type = generate_type_schema(ty);
545 quote! { ::omni_schema_core::types::EnumVariant::newtype(#variant_name_str, #schema_type) }
546 }
547 Fields::Unnamed(fields) => {
548 let types: Vec<_> = fields.unnamed.iter().map(|f| {
549 let ty = &f.ty;
550 generate_type_schema(ty)
551 }).collect();
552 quote! { ::omni_schema_core::types::EnumVariant::tuple(#variant_name_str, vec![#(#types),*]) }
553 }
554 Fields::Named(fields) => {
555 let field_tokens = generate_variant_fields(&fields.named, type_attrs)?;
556 quote! {
557 {
558 let mut fields = ::omni_schema_core::__private::IndexMap::new();
559 #field_tokens
560 ::omni_schema_core::types::EnumVariant::struct_variant(#variant_name_str, fields)
561 }
562 }
563 }
564 };
565
566 let mut variant_builder = variant_def;
567
568 if let Some(ref desc) = variant_attrs.description {
569 variant_builder = quote! { #variant_builder.with_description(#desc) };
570 }
571
572 if variant_attrs.deprecated {
573 variant_builder = quote! { #variant_builder.deprecated() };
574 }
575
576 variant_tokens.extend(quote! {
577 enum_def = enum_def.with_variant(#variant_builder);
578 });
579 }
580
581 let description = match &type_attrs.description {
582 Some(desc) => quote! { .with_description(#desc) },
583 None => quote! {},
584 };
585
586 let deprecated = if type_attrs.deprecated {
587 quote! { .deprecated() }
588 } else {
589 quote! {}
590 };
591
592 Ok(quote! {
593 {
594 let mut enum_def = ::omni_schema_core::types::EnumDefinition::new(#representation);
595 #variant_tokens
596 ::omni_schema_core::SchemaDefinition::new(
597 #name_str,
598 ::omni_schema_core::types::SchemaType::Enum(enum_def),
599 )
600 #description
601 #deprecated
602 }
603 })
604}
605
606fn generate_variant_fields<'a>(
607 fields: impl IntoIterator<Item = &'a Field>,
608 type_attrs: &TypeAttributes,
609) -> syn::Result<TokenStream2> {
610 let mut tokens = TokenStream2::new();
611
612 for field in fields {
613 let field_attrs = parse_field_attributes(&field.attrs)?;
614
615 if field_attrs.skip {
616 continue;
617 }
618
619 let field_name = field.ident.as_ref().unwrap();
620 let field_name_str = field_attrs
621 .rename
622 .as_deref()
623 .map(|s| s.to_string())
624 .unwrap_or_else(|| {
625 apply_rename_rule(&field_name.to_string(), type_attrs.rename_all.as_deref())
626 });
627
628 let original_name_str = field_name.to_string();
629 let ty = &field.ty;
630 let schema_type = generate_type_schema(ty);
631
632 let mut field_builder = quote! {
633 ::omni_schema_core::types::StructField::new(#schema_type, #original_name_str)
634 };
635
636 if let Some(ref desc) = field_attrs.description {
637 field_builder = quote! { #field_builder.with_description(#desc) };
638 }
639
640 if let Some(ref format) = field_attrs.format {
641 field_builder = quote! { #field_builder.with_format(#format) };
642 }
643
644 if field_attrs.deprecated {
645 field_builder = quote! { #field_builder.deprecated() };
646 }
647
648 tokens.extend(quote! {
649 fields.insert(#field_name_str.to_string(), #field_builder);
650 });
651 }
652
653 Ok(tokens)
654}
655
656fn generate_type_schema(ty: &Type) -> TokenStream2 {
657 match ty {
658 Type::Path(type_path) => {
659 let path = &type_path.path;
660 let segment = path.segments.last().unwrap();
661 let type_name = segment.ident.to_string();
662
663 match type_name.as_str() {
664 "bool" => quote! { ::omni_schema_core::types::SchemaType::Primitive(::omni_schema_core::types::PrimitiveType::Bool) },
665 "i8" => quote! { ::omni_schema_core::types::SchemaType::Primitive(::omni_schema_core::types::PrimitiveType::I8) },
666 "i16" => quote! { ::omni_schema_core::types::SchemaType::Primitive(::omni_schema_core::types::PrimitiveType::I16) },
667 "i32" => quote! { ::omni_schema_core::types::SchemaType::Primitive(::omni_schema_core::types::PrimitiveType::I32) },
668 "i64" => quote! { ::omni_schema_core::types::SchemaType::Primitive(::omni_schema_core::types::PrimitiveType::I64) },
669 "i128" => quote! { ::omni_schema_core::types::SchemaType::Primitive(::omni_schema_core::types::PrimitiveType::I128) },
670 "isize" => quote! { ::omni_schema_core::types::SchemaType::Primitive(::omni_schema_core::types::PrimitiveType::Isize) },
671 "u8" => quote! { ::omni_schema_core::types::SchemaType::Primitive(::omni_schema_core::types::PrimitiveType::U8) },
672 "u16" => quote! { ::omni_schema_core::types::SchemaType::Primitive(::omni_schema_core::types::PrimitiveType::U16) },
673 "u32" => quote! { ::omni_schema_core::types::SchemaType::Primitive(::omni_schema_core::types::PrimitiveType::U32) },
674 "u64" => quote! { ::omni_schema_core::types::SchemaType::Primitive(::omni_schema_core::types::PrimitiveType::U64) },
675 "u128" => quote! { ::omni_schema_core::types::SchemaType::Primitive(::omni_schema_core::types::PrimitiveType::U128) },
676 "usize" => quote! { ::omni_schema_core::types::SchemaType::Primitive(::omni_schema_core::types::PrimitiveType::Usize) },
677 "f32" => quote! { ::omni_schema_core::types::SchemaType::Primitive(::omni_schema_core::types::PrimitiveType::F32) },
678 "f64" => quote! { ::omni_schema_core::types::SchemaType::Primitive(::omni_schema_core::types::PrimitiveType::F64) },
679 "char" => quote! { ::omni_schema_core::types::SchemaType::Primitive(::omni_schema_core::types::PrimitiveType::Char) },
680 "String" => quote! { ::omni_schema_core::types::SchemaType::Primitive(::omni_schema_core::types::PrimitiveType::String) },
681
682 "Option" => {
683 if let PathArguments::AngleBracketed(args) = &segment.arguments {
684 if let Some(GenericArgument::Type(inner_ty)) = args.args.first() {
685 let inner = generate_type_schema(inner_ty);
686 return quote! { ::omni_schema_core::types::SchemaType::Option(Box::new(#inner)) };
687 }
688 }
689 quote! { ::omni_schema_core::types::SchemaType::Any }
690 }
691 "Vec" => {
692 if let PathArguments::AngleBracketed(args) = &segment.arguments {
693 if let Some(GenericArgument::Type(inner_ty)) = args.args.first() {
694 let inner = generate_type_schema(inner_ty);
695 return quote! { ::omni_schema_core::types::SchemaType::Array(Box::new(#inner)) };
696 }
697 }
698 quote! { ::omni_schema_core::types::SchemaType::Any }
699 }
700 "HashSet" | "BTreeSet" => {
701 if let PathArguments::AngleBracketed(args) = &segment.arguments {
702 if let Some(GenericArgument::Type(inner_ty)) = args.args.first() {
703 let inner = generate_type_schema(inner_ty);
704 return quote! { ::omni_schema_core::types::SchemaType::Set(Box::new(#inner)) };
705 }
706 }
707 quote! { ::omni_schema_core::types::SchemaType::Any }
708 }
709 "HashMap" | "BTreeMap" => {
710 if let PathArguments::AngleBracketed(args) = &segment.arguments {
711 let mut args_iter = args.args.iter();
712 args_iter.next(); if let Some(GenericArgument::Type(value_ty)) = args_iter.next() {
714 let value = generate_type_schema(value_ty);
715 return quote! { ::omni_schema_core::types::SchemaType::Map(Box::new(#value)) };
716 }
717 }
718 quote! { ::omni_schema_core::types::SchemaType::Any }
719 }
720 "Box" => {
721 if let PathArguments::AngleBracketed(args) = &segment.arguments {
722 if let Some(GenericArgument::Type(inner_ty)) = args.args.first() {
723 return generate_type_schema(inner_ty);
724 }
725 }
726 quote! { ::omni_schema_core::types::SchemaType::Any }
727 }
728
729 "Value" => quote! { ::omni_schema_core::types::SchemaType::Any },
730
731 _ => {
732 let type_name_str = type_name;
733 quote! { ::omni_schema_core::types::SchemaType::Reference(#type_name_str.to_string()) }
734 }
735 }
736 }
737 Type::Reference(type_ref) => {
738 if let Type::Path(path) = &*type_ref.elem {
739 if let Some(segment) = path.path.segments.last() {
740 if segment.ident == "str" {
741 return quote! { ::omni_schema_core::types::SchemaType::Primitive(::omni_schema_core::types::PrimitiveType::String) };
742 }
743 }
744 }
745 generate_type_schema(&type_ref.elem)
746 }
747 Type::Tuple(tuple) => {
748 if tuple.elems.is_empty() {
749 quote! { ::omni_schema_core::types::SchemaType::Unit }
750 } else {
751 let elem_types: Vec<_> = tuple.elems.iter().map(|ty| {
752 let inner = generate_type_schema(ty);
753 quote! { Box::new(#inner) }
754 }).collect();
755 quote! { ::omni_schema_core::types::SchemaType::Tuple(vec![#(#elem_types),*]) }
756 }
757 }
758 Type::Array(array) => {
759 let inner = generate_type_schema(&array.elem);
760 quote! { ::omni_schema_core::types::SchemaType::Array(Box::new(#inner)) }
761 }
762 Type::Slice(slice) => {
763 let inner = generate_type_schema(&slice.elem);
764 quote! { ::omni_schema_core::types::SchemaType::Array(Box::new(#inner)) }
765 }
766 _ => {
767 quote! { ::omni_schema_core::types::SchemaType::Any }
768 }
769 }
770}
771
772fn apply_rename_rule(name: &str, rule: Option<&str>) -> String {
773 match rule {
774 Some("camelCase") => to_camel_case(name),
775 Some("snake_case") => to_snake_case(name),
776 Some("PascalCase") => to_pascal_case(name),
777 Some("SCREAMING_SNAKE_CASE") => to_snake_case(name).to_uppercase(),
778 Some("kebab-case") => to_snake_case(name).replace('_', "-"),
779 Some("SCREAMING-KEBAB-CASE") => to_snake_case(name).replace('_', "-").to_uppercase(),
780 Some("lowercase") => name.to_lowercase(),
781 Some("UPPERCASE") => name.to_uppercase(),
782 _ => name.to_string(),
783 }
784}
785
786fn to_camel_case(s: &str) -> String {
787 let pascal = to_pascal_case(s);
788 let mut chars = pascal.chars();
789 match chars.next() {
790 None => String::new(),
791 Some(first) => first.to_lowercase().chain(chars).collect(),
792 }
793}
794
795fn to_pascal_case(s: &str) -> String {
796 let mut result = String::with_capacity(s.len());
797 let mut capitalize_next = true;
798
799 for c in s.chars() {
800 if c == '_' || c == '-' || c == ' ' {
801 capitalize_next = true;
802 } else if capitalize_next {
803 result.extend(c.to_uppercase());
804 capitalize_next = false;
805 } else {
806 result.push(c);
807 }
808 }
809
810 result
811}
812
813fn to_snake_case(s: &str) -> String {
814 let mut result = String::with_capacity(s.len() + 4);
815 let mut prev_was_uppercase = false;
816 let mut prev_was_separator = true;
817
818 for c in s.chars() {
819 if c == '-' || c == ' ' {
820 result.push('_');
821 prev_was_separator = true;
822 prev_was_uppercase = false;
823 } else if c.is_uppercase() {
824 if !prev_was_separator && !prev_was_uppercase {
825 result.push('_');
826 }
827 result.extend(c.to_lowercase());
828 prev_was_uppercase = true;
829 prev_was_separator = false;
830 } else {
831 result.push(c);
832 prev_was_uppercase = false;
833 prev_was_separator = c == '_';
834 }
835 }
836
837 result
838}