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