1use crate::resource_specification::*;
2use quote::quote;
3
4pub struct Target<'a> {
5 tag_definition: TagDefinition<'a>,
6 vendor_map: VendorMap<'a>,
7}
8
9impl<'a> Target<'a> {
10 pub fn for_services(
11 specification: &'a ResourceSpecification<'a>,
12 selected_services: std::collections::BTreeSet<ServiceIdentifier<'a>>,
13 ) -> Self {
14 fn mk_service_definition<'a>(
15 resource_type_name: &'a ResourceTypeName,
16 resource_type: &'a ResourceType,
17 ) -> ServiceDefinition<'a> {
18 ServiceDefinition {
19 resource_type_map: [(resource_type_name, resource_type)].into(),
20 resource_property_type_map: [].into(),
21 }
22 }
23 let mut vendor_map = VendorMap::new();
24
25 for (resource_type_name, resource_type) in &specification.resource_types {
26 let target_service = &resource_type_name.service;
27
28 if !selected_services.contains(target_service) {
29 continue;
30 }
31
32 let vendor_name = &target_service.vendor_name;
33 let service_name = &target_service.service_name;
34
35 vendor_map
36 .entry(vendor_name)
37 .and_modify(|service_map: &mut ServiceMap| {
38 service_map
39 .entry(service_name)
40 .and_modify(|service_definition| {
41 service_definition
42 .resource_type_map
43 .insert(resource_type_name, resource_type);
44 })
45 .or_insert_with(|| {
46 mk_service_definition(resource_type_name, resource_type)
47 });
48 })
49 .or_insert_with(|| {
50 [(
51 service_name,
52 mk_service_definition(resource_type_name, resource_type),
53 )]
54 .into()
55 });
56 }
57
58 let mut tag_definition: Option<TagDefinition> = None;
59
60 for (property_type_name, property_type) in &specification.property_types {
61 match property_type_name {
62 PropertyTypeName::Tag => {
63 tag_definition = Some(TagDefinition(property_type));
64 }
65 PropertyTypeName::PropertyTypeName(resource_property_type_name) => {
66 let vendor_name = &resource_property_type_name.vendor_name;
67 let service_name = &resource_property_type_name.service_name;
68
69 let service_identifier = ServiceIdentifier {
70 vendor_name: vendor_name.clone(),
71 service_name: service_name.clone(),
72 };
73
74 if !selected_services.contains(&service_identifier) {
75 continue;
76 }
77
78 vendor_map
79 .entry(&resource_property_type_name.vendor_name)
80 .and_modify(|service_map: &mut ServiceMap| {
81 service_map
82 .entry(service_name)
83 .and_modify(|service_definition| {
84 service_definition.add_resource_property_type(
85 resource_property_type_name,
86 property_type,
87 )
88 });
89 })
90 .or_insert_with(|| panic!("Property without resource"));
91 }
92 }
93 }
94
95 Self {
96 tag_definition: tag_definition.expect("Tag property missing"),
97 vendor_map,
98 }
99 }
100}
101
102pub struct ServiceDefinition<'a> {
103 pub resource_type_map: ResourceTypeMap<'a>,
104 pub resource_property_type_map: ResourcePropertyTypeMap<'a>,
105}
106
107impl<'a> ServiceDefinition<'a> {
108 pub fn add_resource_property_type(
109 &mut self,
110 resource_property_type_name: &'a ResourcePropertyTypeName<'a>,
111 property_type: &'a PropertyType<'a>,
112 ) {
113 self.resource_property_type_map
114 .entry(&resource_property_type_name.resource_name)
115 .and_modify(|property_type_map| {
116 property_type_map.insert(&resource_property_type_name.property_name, property_type);
117 })
118 .or_insert([(&resource_property_type_name.property_name, property_type)].into());
119 }
120}
121
122pub struct TagDefinition<'a>(pub &'a PropertyType<'a>);
123
124impl quote::ToTokens for TagDefinition<'_> {
125 fn to_tokens(&self, stream: &mut proc_macro2::TokenStream) {
126 let property_name = PropertyName("Tag");
127
128 stream.extend(property_type_token_stream(
129 &PropertyTypeName::Tag,
130 &property_name,
131 self.0,
132 ))
133 }
134}
135
136pub type ServiceMap<'a> = std::collections::BTreeMap<&'a ServiceName<'a>, ServiceDefinition<'a>>;
137
138type ResourceTypeMap<'a> =
139 std::collections::BTreeMap<&'a ResourceTypeName<'a>, &'a ResourceType<'a>>;
140
141type ResourcePropertyTypeMap<'a> =
142 std::collections::BTreeMap<&'a ResourceName<'a>, PropertyTypeMap<'a>>;
143
144type PropertyTypeMap<'a> = std::collections::BTreeMap<&'a PropertyName<'a>, &'a PropertyType<'a>>;
145
146pub type VendorMap<'a> = std::collections::BTreeMap<&'a VendorName<'a>, ServiceMap<'a>>;
147
148pub fn token_stream(target: Target) -> proc_macro2::TokenStream {
149 let mut stream = proc_macro2::TokenStream::new();
150
151 for (vendor_name, service_map) in target.vendor_map {
152 let vendor_module_name = VendorModuleName::new(vendor_name);
153
154 let services = service_map_token_stream(vendor_name, service_map);
155
156 let module_stream: proc_macro2::TokenStream = quote! {
157 pub mod #vendor_module_name {
158 #services
159 }
160 };
161
162 stream.extend(module_stream);
163 }
164
165 let tag_definition = target.tag_definition;
166
167 quote! {
168 pub mod cloudformation {
169 #tag_definition
170 #stream
171 }
172 }
173}
174
175fn service_map_token_stream(
176 vendor_name: &VendorName<'_>,
177 service_map: ServiceMap,
178) -> proc_macro2::TokenStream {
179 let mut stream = proc_macro2::TokenStream::new();
180
181 for (service_name, service_definition) in service_map {
182 let service_module_name = ServiceModuleName::new(service_name);
183
184 let properties = resource_property_type_map_token_stream(
185 &ServiceIdentifier {
186 vendor_name: vendor_name.clone(),
187 service_name: service_name.clone(),
188 },
189 service_definition.resource_property_type_map,
190 );
191 let resources = resource_type_map_token_stream(
192 &service_module_name,
193 service_definition.resource_type_map,
194 );
195
196 let service_stream: proc_macro2::TokenStream = quote! {
197 pub mod #service_module_name {
198 #properties
199 #resources
200 }
201 };
202
203 stream.extend(service_stream);
204 }
205 stream
206}
207
208fn resource_property_type_map_token_stream(
209 service: &ServiceIdentifier<'_>,
210 resource_property_type_map: ResourcePropertyTypeMap,
211) -> proc_macro2::TokenStream {
212 let mut stream = proc_macro2::TokenStream::new();
213
214 for (resource_name, property_type_map) in resource_property_type_map {
215 stream.extend(property_type_map_token_stream(
216 service,
217 resource_name,
218 property_type_map,
219 ))
220 }
221
222 stream
223}
224
225fn property_type_map_token_stream(
226 service: &ServiceIdentifier<'_>,
227 resource_name: &ResourceName<'_>,
228 property_type_map: PropertyTypeMap<'_>,
229) -> proc_macro2::TokenStream {
230 let resource_module_name = ResourceModuleName::new(resource_name);
231
232 let property_types: Vec<_> = property_type_map
233 .into_iter()
234 .map(|(property_name, property_type)| {
235 property_type_token_stream(
236 &PropertyTypeName::PropertyTypeName(ResourcePropertyTypeName {
237 vendor_name: service.vendor_name.clone(),
238 service_name: service.service_name.clone(),
239 resource_name: resource_name.clone(),
240 property_name: property_name.clone(),
241 }),
242 property_name,
243 property_type,
244 )
245 })
246 .collect();
247
248 quote! {
249 pub mod #resource_module_name {
250 #(#property_types)*
251 }
252 }
253}
254
255fn property_type_token_stream(
256 property_type_name: &PropertyTypeName<'_>,
257 property_name: &PropertyName<'_>,
258 property_type: &PropertyType<'_>,
259) -> proc_macro2::TokenStream {
260 let struct_name = property_name_struct_name(property_name);
261 let macro_name = quote::format_ident!("{property_name}");
262
263 let properties = match &property_type.properties {
264 Some(properties) => properties,
265 None => &PropertyTypePropertiesMap::new(),
266 };
267
268 let documentation = property_type.documentation.as_str();
269
270 let fields: Vec<_> = properties
271 .iter()
272 .map(|(property_name, property_type_property)| {
273 let field_name = mk_field_name(property_name.as_str());
274
275 let field_type = property_type_property_token_stream(property_type_property);
276
277 quote! {
278 pub #field_name : #field_type
279 }
280 })
281 .collect();
282
283 let to_values: Vec<_> = properties
284 .iter()
285 .map(|(property_name, property_type)| mk_to_value(property_name, property_type))
286 .collect();
287
288 let property_type_name_str: &str = &property_type_name.to_string();
289
290 quote! {
291 #[doc = #documentation]
292 pub struct #struct_name {
293 #(#fields),*
294 }
295
296 #[allow(unused_macros)]
297 macro_rules! #macro_name {
298 ($($field:ident : $value:expr),* $(,)?) => {
299 stratosphere::generator::construct_property_type!(#property_type_name_str $($field $value)*)
300 }
301 }
302
303 pub(crate) use #macro_name;
304
305 impl stratosphere::value::ToValue for #struct_name {
306 fn to_value(&self) -> serde_json::Value {
307 let mut properties = serde_json::Map::new();
308
309 #(#to_values)*
310
311 properties.into()
312 }
313 }
314 }
315}
316
317fn property_type_property_token_stream(
318 property_type_property: &PropertyTypeProperty,
319) -> proc_macro2::TokenStream {
320 let field_type = match property_type_property {
321 PropertyTypeProperty {
322 documentation: _,
323 duplicates_allowed: None,
324 item_type: None,
325 primitive_item_type: None,
326 primitive_type: Some(primitive_type),
327 r#type: None,
328 required: _,
329 update_type: _,
330 } => mk_primitive_type(primitive_type),
331 PropertyTypeProperty {
332 documentation: _,
333 duplicates_allowed: _,
334 item_type: Some(TypeReference::Tag),
335 primitive_item_type: None,
336 primitive_type: None,
337 r#type: Some(TypeReference::List),
338 required: _,
339 update_type: _,
340 } => quote! { Vec<super::super::super::Tag_> },
341 PropertyTypeProperty {
342 documentation: _,
343 duplicates_allowed: _,
344 item_type: Some(TypeReference::Subproperty(property_name)),
345 primitive_item_type: None,
346 primitive_type: None,
347 r#type: Some(TypeReference::List),
348 required: _,
349 update_type: _,
350 } => {
351 let struct_name = property_name_struct_name(property_name);
352 quote! { Vec<#struct_name> }
353 }
354 PropertyTypeProperty {
355 documentation: _,
356 duplicates_allowed: _,
357 item_type: None,
358 primitive_item_type: Some(primitive_item_type),
359 primitive_type: None,
360 r#type: Some(TypeReference::List),
361 required: _,
362 update_type: _,
363 } => mk_primitive_list_type(primitive_item_type),
364 PropertyTypeProperty {
365 documentation: _,
366 duplicates_allowed: None,
367 item_type: None,
368 primitive_item_type: None,
369 primitive_type: None,
370 r#type: Some(TypeReference::Subproperty(property_name)),
371 required: _,
372 update_type: _,
373 } => {
374 let struct_name = property_name_struct_name(property_name);
375 quote! { #struct_name }
376 }
377 PropertyTypeProperty {
378 documentation: _,
379 duplicates_allowed: None,
380 item_type: None,
381 primitive_item_type: Some(item_type),
382 primitive_type: None,
383 r#type: Some(TypeReference::Map),
384 required: _,
385 update_type: _,
386 } => mk_primitive_map_type(item_type),
387 other => panic!("Unsupported property type property: {other:#?}"),
388 };
389
390 mk_option(property_type_property.required, field_type)
391}
392
393fn mk_option(required: bool, stream: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
394 if !required {
395 quote! { Option<#stream> }
396 } else {
397 stream
398 }
399}
400
401fn resource_type_map_token_stream(
402 service_module_name: &ServiceModuleName,
403 resource_type_map: ResourceTypeMap,
404) -> proc_macro2::TokenStream {
405 let mut stream = proc_macro2::TokenStream::new();
406
407 for (resource_type_name, resource_type) in resource_type_map {
408 stream.extend(resource_type_token_stream(
409 service_module_name,
410 resource_type_name,
411 resource_type,
412 ))
413 }
414
415 stream
416}
417
418trait IsRequired {
419 fn required(&self) -> bool;
420}
421
422impl IsRequired for &ResourceTypeProperty<'_> {
423 fn required(&self) -> bool {
424 self.required
425 }
426}
427
428impl IsRequired for &PropertyTypeProperty<'_> {
429 fn required(&self) -> bool {
430 self.required
431 }
432}
433
434fn mk_to_value(
435 property_name: impl AsRef<str>,
436 property_type: impl IsRequired,
437) -> proc_macro2::TokenStream {
438 let field_name = mk_field_name(property_name.as_ref());
439 let key = property_name.as_ref();
440
441 if property_type.required() {
442 quote! {
443 properties
444 .insert(
445 #key.to_string(),
446 stratosphere::value::ToValue::to_value(&self.#field_name)
447 );
448 }
449 } else {
450 quote! {
451 if let Some(ref value) = self.#field_name {
452 properties.insert(
453 #key.to_string(),
454 stratosphere::value::ToValue::to_value(value)
455 );
456 }
457 }
458 }
459}
460
461fn resource_type_token_stream(
462 service_module_name: &ServiceModuleName,
463 resource_type_name: &ResourceTypeName<'_>,
464 resource_type: &ResourceType<'_>,
465) -> proc_macro2::TokenStream {
466 let resource_name_ref = resource_type_name.resource_name.as_ref();
467
468 let struct_name = quote::format_ident!("{resource_name_ref}_");
469
470 let macro_name = quote::format_ident!("{resource_name_ref}");
471
472 let resource_module_name = ResourceModuleName::new(&resource_type_name.resource_name);
473
474 let documentation = resource_type.documentation.as_str();
475
476 let fields: Vec<_> = resource_type
477 .properties
478 .iter()
479 .map(|(property_name, property_type)| {
480 resource_property_type_token_stream(
481 service_module_name,
482 &resource_module_name,
483 property_name,
484 property_type,
485 )
486 })
487 .collect();
488
489 let to_values: Vec<_> = resource_type
490 .properties
491 .iter()
492 .map(|(property_name, property_type)| mk_to_value(property_name, property_type))
493 .collect();
494
495 let resource_type_name_str: &str = &resource_type_name.to_string();
496
497 let stream = quote! {
498 #[doc = #documentation]
499 pub struct #struct_name {
500 #(#fields),*
501 }
502
503 #[allow(unused_macros)]
504 macro_rules! #macro_name {
505 ($($field:ident : $value:expr),* $(,)?) => {
506 stratosphere::generator::construct_resource_type!(#resource_type_name_str $($field $value)*)
507 }
508 }
509
510 pub(crate) use #macro_name;
511
512 impl stratosphere::template::ToResource for #struct_name {
513 const RESOURCE_TYPE_NAME: stratosphere::resource_specification::ResourceTypeName<'static> =
514 #resource_type_name;
515
516 fn to_resource_properties(&self) -> stratosphere::template::ResourceProperties {
517 let mut properties = stratosphere::template::ResourceProperties::new();
518
519 #(#to_values)*
520
521 properties
522 }
523 }
524 };
525
526 stream
527}
528
529fn resource_property_type_token_stream(
530 service_module_name: &ServiceModuleName,
531 resource_module_name: &ResourceModuleName,
532 resource_type_property_name: &ResourceTypePropertyName<'_>,
533 resource_type_property: &ResourceTypeProperty<'_>,
534) -> proc_macro2::TokenStream {
535 let field_name = mk_field_name(resource_type_property_name);
536
537 let property_type = resource_type_property_token_stream(
538 service_module_name,
539 resource_module_name,
540 resource_type_property,
541 );
542
543 let field_type = mk_option(resource_type_property.required, property_type);
544
545 quote! {
546 pub #field_name: #field_type
547 }
548}
549
550fn resource_type_property_token_stream(
551 service_module_name: &ServiceModuleName,
552 resource_module_name: &ResourceModuleName,
553 resource_type_property: &ResourceTypeProperty<'_>,
554) -> proc_macro2::TokenStream {
555 match resource_type_property {
556 ResourceTypeProperty {
557 primitive_type: Some(primitive_type),
558 item_type: None,
559 duplicates_allowed: None,
560 primitive_item_type: None,
561 documentation: _,
562 update_type: _,
563 required: _,
564 r#type: None,
565 } => mk_primitive_type(primitive_type),
566 ResourceTypeProperty {
567 primitive_type: None,
568 item_type: Some(item_type),
569 duplicates_allowed: _,
570 primitive_item_type,
571 documentation: _,
572 update_type: _,
573 required: _,
574 r#type: Some(TypeReference::List),
575 } => mk_list_type(
576 service_module_name,
577 resource_module_name,
578 item_type,
579 primitive_item_type.as_ref(),
580 ),
581 ResourceTypeProperty {
582 primitive_type: None,
583 item_type: None,
584 duplicates_allowed: _,
585 primitive_item_type: Some(primitive_item_type),
586 documentation: _,
587 update_type: _,
588 required: _,
589 r#type: Some(TypeReference::List),
590 } => mk_primitive_list_type(primitive_item_type),
591 ResourceTypeProperty {
592 primitive_type: None,
593 item_type: None,
594 duplicates_allowed: None,
595 primitive_item_type: None,
596 documentation: _,
597 update_type: _,
598 required: _,
599 r#type: Some(TypeReference::Subproperty(subproperty_name)),
600 } => mk_subproperty(service_module_name, resource_module_name, subproperty_name),
601 other => panic!("Unsupported property type: {other:#?}"),
602 }
603}
604
605fn mk_list_type(
606 service_module_name: &ServiceModuleName,
607 resource_module_name: &ResourceModuleName,
608 item_type: &TypeReference,
609 primitive_item_type: Option<&PrimitiveItemType>,
610) -> proc_macro2::TokenStream {
611 let item_type = mk_type_reference_type(
612 service_module_name,
613 resource_module_name,
614 item_type,
615 primitive_item_type,
616 );
617
618 quote! { Vec<#item_type> }
619}
620
621fn mk_primitive_list_type(primitive_item_type: &PrimitiveItemType) -> proc_macro2::TokenStream {
622 let item_type = match primitive_item_type {
623 PrimitiveItemType::Json => quote! { serde_json::Value },
624 PrimitiveItemType::Double => quote! { f64 },
625 PrimitiveItemType::Integer => quote! { i64 },
626 PrimitiveItemType::String => quote! { stratosphere::value::ExpString },
627 };
628
629 quote! { Vec<#item_type> }
630}
631
632fn mk_primitive_map_type(primitive_item_type: &PrimitiveItemType) -> proc_macro2::TokenStream {
633 let item_type = match primitive_item_type {
634 PrimitiveItemType::Json => quote! { serde_json::Value },
635 PrimitiveItemType::Double => quote! { f64 },
636 PrimitiveItemType::Integer => quote! { i64 },
637 PrimitiveItemType::String => quote! { stratosphere::value::ExpString },
638 };
639
640 quote! { std::collections::BTreeMap<String, #item_type> }
641}
642
643fn mk_type_reference_type(
644 service_module_name: &ServiceModuleName,
645 resource_module_name: &ResourceModuleName,
646 item_type: &TypeReference,
647 primitive_item_type: Option<&PrimitiveItemType>,
648) -> proc_macro2::TokenStream {
649 match item_type {
650 TypeReference::Subproperty(name) => {
651 mk_subproperty(service_module_name, resource_module_name, name)
652 }
653 TypeReference::Tag => quote! { super::super::Tag_ },
654 TypeReference::List => match primitive_item_type {
655 Some(PrimitiveItemType::Json) => quote! { Vec<serde_json::Value> },
656 Some(PrimitiveItemType::Double) => quote! { Vec<f64> },
657 Some(PrimitiveItemType::Integer) => quote! { Vec<i64> },
658 Some(PrimitiveItemType::String) => quote! { Vec<stratosphere::value::ExpString> },
659 None => panic!("TypeReference::List requires primitive_item_type to be specified"),
660 },
661 TypeReference::Map => match primitive_item_type {
662 Some(PrimitiveItemType::Json) => {
663 quote! { std::collections::BTreeMap<String, serde_json::Value> }
664 }
665 Some(PrimitiveItemType::Double) => {
666 quote! { std::collections::BTreeMap<String, f64> }
667 }
668 Some(PrimitiveItemType::Integer) => {
669 quote! { std::collections::BTreeMap<String, i64> }
670 }
671 Some(PrimitiveItemType::String) => {
672 quote! { std::collections::BTreeMap<String, stratosphere::value::ExpString> }
673 }
674 None => panic!("TypeReference::Map requires primitive_item_type to be specified"),
675 },
676 }
677}
678
679pub struct VendorModuleName(proc_macro2::Ident);
680
681impl VendorModuleName {
682 pub fn new(vendor_name: &VendorName<'_>) -> Self {
683 Self(quote::format_ident!(
684 "{}",
685 vendor_name.as_ref().to_lowercase()
686 ))
687 }
688}
689
690impl quote::ToTokens for VendorModuleName {
691 fn to_tokens(&self, stream: &mut proc_macro2::TokenStream) {
692 let ident = &self.0;
693 stream.extend(quote! { #ident })
694 }
695}
696
697pub struct ServiceModuleName(proc_macro2::Ident);
698
699impl ServiceModuleName {
700 pub fn new(service_name: &ServiceName<'_>) -> Self {
701 Self(quote::format_ident!(
702 "{}",
703 service_name.as_ref().to_lowercase()
704 ))
705 }
706}
707
708impl quote::ToTokens for ServiceModuleName {
709 fn to_tokens(&self, stream: &mut proc_macro2::TokenStream) {
710 let ident = &self.0;
711 stream.extend(quote! { #ident })
712 }
713}
714
715pub struct ResourceModuleName(proc_macro2::Ident);
716
717impl ResourceModuleName {
718 pub fn new(resource_name: &ResourceName<'_>) -> Self {
719 Self(quote::format_ident!(
720 "{}",
721 resource_name.as_ref().to_lowercase()
722 ))
723 }
724}
725
726impl quote::ToTokens for ResourceModuleName {
727 fn to_tokens(&self, stream: &mut proc_macro2::TokenStream) {
728 let ident = &self.0;
729 stream.extend(quote! { #ident })
730 }
731}
732
733fn mk_subproperty(
734 service_module_name: &ServiceModuleName,
735 resource_module_name: &ResourceModuleName,
736 property_name: &PropertyName,
737) -> proc_macro2::TokenStream {
738 let property_name = property_name_struct_name(property_name);
739
740 quote! {
741 super::#service_module_name::#resource_module_name::#property_name
742 }
743}
744
745fn mk_primitive_type(primitive_type: &PrimitiveType) -> proc_macro2::TokenStream {
746 match primitive_type {
747 PrimitiveType::Boolean => quote! { stratosphere::value::ExpBool },
748 PrimitiveType::Double => quote! { f64 },
749 PrimitiveType::Integer => quote! { i64 },
750 PrimitiveType::Long => quote! { i64 },
751 PrimitiveType::String => quote! { stratosphere::value::ExpString },
752 PrimitiveType::Timestamp => quote! { chrono::DateTime<chrono::Utc> },
753 PrimitiveType::Json => quote! { serde_json::Value },
754 }
755}
756
757pub fn to_snake_case(input: &str) -> String {
759 if input.is_empty() {
760 return String::new();
761 }
762
763 let mut result = String::new();
764 let mut it = input.chars().peekable();
765
766 let mut prev_is_lower = false;
767
768 let first = it.next().unwrap();
769 result.push(if first.is_uppercase() {
770 first.to_lowercase().next().unwrap()
771 } else {
772 prev_is_lower = true;
773 first
774 });
775
776 while let Some(ch) = it.next() {
777 let is_upper = ch.is_uppercase();
778 let next_is_lower = it.peek().is_some_and(|&next_ch| next_ch.is_lowercase());
779
780 if (next_is_lower || prev_is_lower) && is_upper {
781 result.push('_');
782 }
783
784 result.push(if is_upper {
785 ch.to_lowercase().next().unwrap()
786 } else {
787 ch
788 });
789
790 prev_is_lower = !is_upper;
791 }
792
793 result
794}
795
796pub fn mk_field_name(name: impl AsRef<str>) -> syn::Ident {
797 let name = name.as_ref();
798 let snake_case_name = to_snake_case(name);
799
800 if snake_case_name == "self" {
801 return quote::format_ident!("self_value");
802 }
803
804 if syn::parse_str::<syn::Ident>(&snake_case_name).is_err() {
805 quote::format_ident!("r#{}", snake_case_name)
806 } else {
807 quote::format_ident!("{}", snake_case_name)
808 }
809}
810
811pub fn resource_type_struct_name(resource_type_name: &ResourceTypeName<'_>) -> syn::Ident {
812 quote::format_ident!("{}_", resource_type_name.resource_name.as_str())
813}
814
815pub fn property_name_struct_name(property_name: &PropertyName<'_>) -> syn::Ident {
816 quote::format_ident!("{property_name}_")
817}
818
819#[cfg(test)]
820mod tests {
821 use super::*;
822
823 fn assert_snake_case(original: &str, expected: &str) {
824 assert_eq!(
825 to_snake_case(original),
826 expected,
827 "Original: {original}, expected: {expected}"
828 );
829 }
830
831 #[test]
832 fn test_pascal_case() {
833 assert_snake_case("HelloWorld", "hello_world");
834 }
835
836 #[test]
837 fn test_camel_case() {
838 assert_snake_case("helloWorld", "hello_world");
839 }
840
841 #[test]
842 fn test_all_upper() {
843 assert_snake_case("XMLHTTPRequest", "xmlhttp_request");
844 }
845
846 #[test]
847 fn test_single_word() {
848 assert_snake_case("Hello", "hello");
849 }
850
851 #[test]
852 fn test_lowercase() {
853 assert_snake_case("hello", "hello");
854 }
855
856 #[test]
857 fn test_keyword_detection_and_escaping() {
858 assert_eq!(mk_field_name("Type").to_string(), "r#type");
859 assert_eq!(mk_field_name("Match").to_string(), "r#match");
860 assert_eq!(mk_field_name("Async").to_string(), "r#async");
861 assert_eq!(mk_field_name("Await").to_string(), "r#await");
862 assert_eq!(mk_field_name("Const").to_string(), "r#const");
863 assert_eq!(mk_field_name("Loop").to_string(), "r#loop");
864 assert_eq!(mk_field_name("Return").to_string(), "r#return");
865 assert_eq!(mk_field_name("Impl").to_string(), "r#impl");
866 assert_eq!(mk_field_name("Mod").to_string(), "r#mod");
867 assert_eq!(mk_field_name("Pub").to_string(), "r#pub");
868 assert_eq!(mk_field_name("Use").to_string(), "r#use");
869 assert_eq!(mk_field_name("Fn").to_string(), "r#fn");
870 assert_eq!(mk_field_name("Static").to_string(), "r#static");
871 assert_eq!(mk_field_name("Mut").to_string(), "r#mut");
872 assert_eq!(mk_field_name("Ref").to_string(), "r#ref");
873 assert_eq!(mk_field_name("Dyn").to_string(), "r#dyn");
874 assert_eq!(mk_field_name("Self").to_string(), "self_value");
875 assert_eq!(mk_field_name("Abstract").to_string(), "r#abstract");
876 assert_eq!(mk_field_name("Final").to_string(), "r#final");
877 assert_eq!(mk_field_name("Override").to_string(), "r#override");
878 assert_eq!(mk_field_name("Yield").to_string(), "r#yield");
879 assert_eq!(mk_field_name("Try").to_string(), "r#try");
880 assert_eq!(mk_field_name("Gen").to_string(), "gen");
881 assert_eq!(mk_field_name("Union").to_string(), "union");
882 assert_eq!(mk_field_name("FooBar").to_string(), "foo_bar");
883 assert_eq!(mk_field_name("TestValue").to_string(), "test_value");
884 assert_eq!(mk_field_name("MyField").to_string(), "my_field");
885 assert_eq!(
886 mk_field_name("EnableDnsSupport").to_string(),
887 "enable_dns_support"
888 );
889 assert_eq!(mk_field_name("VpcId").to_string(), "vpc_id");
890 }
891}