1use std::{
2 borrow::{Borrow, Cow},
3 cmp::Ordering,
4 fmt::Display,
5 ops::Deref,
6};
7
8use heck::{AsKebabCase, AsPascalCase, AsSnekCase};
9use itertools::Itertools;
10use ploidy_core::{
11 codegen::{
12 UniqueNames,
13 unique::{UniqueNamesScope, WordSegments},
14 },
15 ir::{
16 ExtendableView, InlineTypePathSegment, InlineTypeView, PrimitiveType, SchemaTypeView,
17 StructFieldName, StructFieldNameHint, UntaggedVariantNameHint,
18 },
19};
20use proc_macro2::{Ident, Span, TokenStream};
21use quote::{IdentFragment, ToTokens, TokenStreamExt};
22use ref_cast::{RefCastCustom, ref_cast_custom};
23
24const KEYWORDS: &[&str] = &["crate", "self", "super", "Self"];
26
27#[derive(Clone, Copy, Debug)]
38pub enum CodegenTypeName<'a> {
39 Schema(&'a SchemaTypeView<'a>),
40 Inline(&'a InlineTypeView<'a>),
41}
42
43impl<'a> CodegenTypeName<'a> {
44 #[inline]
45 pub fn into_module_name(self) -> CodegenModuleName<'a> {
46 CodegenModuleName(self)
47 }
48
49 #[inline]
50 pub fn into_sort_key(self) -> CodegenTypeNameSortKey<'a> {
51 CodegenTypeNameSortKey(self)
52 }
53}
54
55impl ToTokens for CodegenTypeName<'_> {
56 fn to_tokens(&self, tokens: &mut TokenStream) {
57 match self {
58 Self::Schema(view) => {
59 let ident = view.extensions().get::<CodegenIdent>().unwrap();
60 CodegenIdentUsage::Type(&ident).to_tokens(tokens);
61 }
62 Self::Inline(view) => {
63 let ident = CodegenIdent::from_segments(view.path().segments);
64 CodegenIdentUsage::Type(&ident).to_tokens(tokens);
65 }
66 }
67 }
68}
69
70#[derive(Clone, Copy, Debug)]
76pub struct CodegenModuleName<'a>(CodegenTypeName<'a>);
77
78impl<'a> CodegenModuleName<'a> {
79 #[inline]
80 pub fn into_type_name(self) -> CodegenTypeName<'a> {
81 self.0
82 }
83
84 pub fn display(&self) -> impl Display {
90 struct DisplayModuleName<'a>(CodegenTypeName<'a>);
91 impl Display for DisplayModuleName<'_> {
92 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93 match self.0 {
94 CodegenTypeName::Schema(view) => {
95 let ident = view.extensions().get::<CodegenIdent>().unwrap();
96 write!(f, "{}", CodegenIdentUsage::Module(&ident).display())
97 }
98 CodegenTypeName::Inline(view) => {
99 let ident = CodegenIdent::from_segments(view.path().segments);
100 write!(f, "{}", CodegenIdentUsage::Module(&ident).display())
101 }
102 }
103 }
104 }
105 DisplayModuleName(self.0)
106 }
107}
108
109impl ToTokens for CodegenModuleName<'_> {
110 fn to_tokens(&self, tokens: &mut TokenStream) {
111 match self.0 {
112 CodegenTypeName::Schema(view) => {
113 let ident = view.extensions().get::<CodegenIdent>().unwrap();
114 CodegenIdentUsage::Module(&ident).to_tokens(tokens);
115 }
116 CodegenTypeName::Inline(view) => {
117 let ident = CodegenIdent::from_segments(view.path().segments);
118 CodegenIdentUsage::Module(&ident).to_tokens(tokens);
119 }
120 }
121 }
122}
123
124#[derive(Clone, Copy, Debug)]
130pub struct CodegenTypeNameSortKey<'a>(CodegenTypeName<'a>);
131
132impl<'a> CodegenTypeNameSortKey<'a> {
133 #[inline]
134 pub fn for_schema(view: &'a SchemaTypeView<'a>) -> Self {
135 Self(CodegenTypeName::Schema(view))
136 }
137
138 #[inline]
139 pub fn for_inline(view: &'a InlineTypeView<'a>) -> Self {
140 Self(CodegenTypeName::Inline(view))
141 }
142
143 #[inline]
144 pub fn into_name(self) -> CodegenTypeName<'a> {
145 self.0
146 }
147}
148
149impl Eq for CodegenTypeNameSortKey<'_> {}
150
151impl Ord for CodegenTypeNameSortKey<'_> {
152 fn cmp(&self, other: &Self) -> Ordering {
153 match (&self.0, &other.0) {
154 (CodegenTypeName::Schema(a), CodegenTypeName::Schema(b)) => a.name().cmp(b.name()),
155 (CodegenTypeName::Inline(a), CodegenTypeName::Inline(b)) => a.path().cmp(&b.path()),
156 (CodegenTypeName::Schema(_), CodegenTypeName::Inline(_)) => Ordering::Less,
157 (CodegenTypeName::Inline(_), CodegenTypeName::Schema(_)) => Ordering::Greater,
158 }
159 }
160}
161
162impl PartialEq for CodegenTypeNameSortKey<'_> {
163 fn eq(&self, other: &Self) -> bool {
164 self.cmp(other).is_eq()
165 }
166}
167
168impl PartialOrd for CodegenTypeNameSortKey<'_> {
169 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
170 Some(self.cmp(other))
171 }
172}
173
174#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
177pub struct CodegenIdent(String);
178
179impl CodegenIdent {
180 pub fn new(s: &str) -> Self {
182 let s = clean(s);
183 if KEYWORDS.contains(&s.as_str()) {
184 Self(format!("_{s}"))
185 } else {
186 Self(s)
187 }
188 }
189
190 pub fn from_segments(segments: &[InlineTypePathSegment<'_>]) -> Self {
192 Self(format!(
193 "{}",
194 segments
195 .iter()
196 .map(CodegenTypePathSegment)
197 .format_with("", |segment, f| f(&segment.display()))
198 ))
199 }
200}
201
202impl AsRef<CodegenIdentRef> for CodegenIdent {
203 fn as_ref(&self) -> &CodegenIdentRef {
204 self
205 }
206}
207
208impl Borrow<CodegenIdentRef> for CodegenIdent {
209 fn borrow(&self) -> &CodegenIdentRef {
210 self
211 }
212}
213
214impl Deref for CodegenIdent {
215 type Target = CodegenIdentRef;
216
217 fn deref(&self) -> &Self::Target {
218 CodegenIdentRef::new(&self.0)
219 }
220}
221
222#[derive(Debug, Eq, Ord, PartialEq, PartialOrd, RefCastCustom)]
224#[repr(transparent)]
225pub struct CodegenIdentRef(str);
226
227impl CodegenIdentRef {
228 #[ref_cast_custom]
229 fn new(s: &str) -> &Self;
230
231 pub fn from_field_name_hint(hint: StructFieldNameHint) -> Cow<'static, Self> {
234 match hint {
235 StructFieldNameHint::Index(index) => {
236 Cow::Owned(CodegenIdent(format!("variant_{index}")))
237 }
238 StructFieldNameHint::AdditionalProperties => {
239 Cow::Borrowed(Self::new("additional_properties"))
240 }
241 }
242 }
243}
244
245impl ToOwned for CodegenIdentRef {
246 type Owned = CodegenIdent;
247
248 fn to_owned(&self) -> Self::Owned {
249 CodegenIdent(self.0.to_owned())
250 }
251}
252
253#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
259pub enum CargoFeature {
260 #[default]
261 Default,
262 Named(CodegenIdent),
263}
264
265impl CargoFeature {
266 #[inline]
267 pub fn from_name(name: &str) -> Self {
268 match name {
269 "default" => Self::Default,
271
272 name => Self::Named(CodegenIdent::new(name)),
276 }
277 }
278
279 #[inline]
280 pub fn as_ident(&self) -> &CodegenIdentRef {
281 match self {
282 Self::Named(name) => name,
283 Self::Default => CodegenIdentRef::new("default"),
284 }
285 }
286
287 #[inline]
288 pub fn display(&self) -> impl Display {
289 match self {
290 Self::Named(name) => AsKebabCase(name.0.as_str()),
291 Self::Default => AsKebabCase("default"),
292 }
293 }
294}
295
296#[derive(Clone, Copy, Debug)]
308pub enum CodegenIdentUsage<'a> {
309 Module(&'a CodegenIdentRef),
310 Type(&'a CodegenIdentRef),
311 Field(&'a CodegenIdentRef),
312 Variant(&'a CodegenIdentRef),
313 Param(&'a CodegenIdentRef),
314 Method(&'a CodegenIdentRef),
315}
316
317impl CodegenIdentUsage<'_> {
318 pub fn display(self) -> impl Display {
324 struct DisplayUsage<'a>(CodegenIdentUsage<'a>);
325 impl Display for DisplayUsage<'_> {
326 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
327 use CodegenIdentUsage::*;
328 match self.0 {
329 Module(name) | Field(name) | Param(name) | Method(name) => {
330 if name.0.starts_with(unicode_ident::is_xid_start) {
331 write!(f, "{}", AsSnekCase(&name.0))
332 } else {
333 write!(f, "_{}", AsSnekCase(&name.0))
337 }
338 }
339 Type(name) | Variant(name) => {
340 if name.0.starts_with(unicode_ident::is_xid_start) {
341 write!(f, "{}", AsPascalCase(&name.0))
342 } else {
343 write!(f, "_{}", AsPascalCase(&name.0))
344 }
345 }
346 }
347 }
348 }
349 DisplayUsage(self)
350 }
351}
352
353impl IdentFragment for CodegenIdentUsage<'_> {
354 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
355 write!(f, "{}", self.display())
356 }
357}
358
359impl ToTokens for CodegenIdentUsage<'_> {
360 fn to_tokens(&self, tokens: &mut TokenStream) {
361 let s = self.display().to_string();
362 let ident = syn::parse_str(&s).unwrap_or_else(|_| Ident::new_raw(&s, Span::call_site()));
363 tokens.append(ident);
364 }
365}
366
367#[derive(Debug)]
369pub struct CodegenIdentScope<'a>(UniqueNamesScope<'a>);
370
371impl<'a> CodegenIdentScope<'a> {
372 pub fn new(arena: &'a UniqueNames) -> Self {
374 Self::with_reserved(arena, &[])
375 }
376
377 pub fn with_reserved(arena: &'a UniqueNames, reserved: &[&str]) -> Self {
380 Self(arena.scope_with_reserved(itertools::chain!(
381 reserved.iter().copied(),
382 KEYWORDS.iter().copied(),
383 std::iter::once("")
384 )))
385 }
386
387 pub fn uniquify(&mut self, name: &str) -> CodegenIdent {
390 CodegenIdent(self.0.uniquify(&clean(name)).into_owned())
391 }
392}
393
394#[derive(Clone, Copy, Debug)]
395pub struct CodegenUntaggedVariantName(pub UntaggedVariantNameHint);
396
397impl ToTokens for CodegenUntaggedVariantName {
398 fn to_tokens(&self, tokens: &mut TokenStream) {
399 use UntaggedVariantNameHint::*;
400 let s = match self.0 {
401 Primitive(PrimitiveType::String) => "String".into(),
402 Primitive(PrimitiveType::I8) => "I8".into(),
403 Primitive(PrimitiveType::U8) => "U8".into(),
404 Primitive(PrimitiveType::I16) => "I16".into(),
405 Primitive(PrimitiveType::U16) => "U16".into(),
406 Primitive(PrimitiveType::I32) => "I32".into(),
407 Primitive(PrimitiveType::U32) => "U32".into(),
408 Primitive(PrimitiveType::I64) => "I64".into(),
409 Primitive(PrimitiveType::U64) => "U64".into(),
410 Primitive(PrimitiveType::F32) => "F32".into(),
411 Primitive(PrimitiveType::F64) => "F64".into(),
412 Primitive(PrimitiveType::Bool) => "Bool".into(),
413 Primitive(PrimitiveType::DateTime) => "DateTime".into(),
414 Primitive(PrimitiveType::UnixTime) => "UnixTime".into(),
415 Primitive(PrimitiveType::Date) => "Date".into(),
416 Primitive(PrimitiveType::Url) => "Url".into(),
417 Primitive(PrimitiveType::Uuid) => "Uuid".into(),
418 Primitive(PrimitiveType::Bytes) => "Bytes".into(),
419 Primitive(PrimitiveType::Binary) => "Binary".into(),
420 Array => "Array".into(),
421 Map => "Map".into(),
422 Index(index) => Cow::Owned(format!("V{index}")),
423 };
424 tokens.append(Ident::new(&s, Span::call_site()));
425 }
426}
427
428#[derive(Clone, Copy, Debug)]
429pub struct CodegenTypePathSegment<'a>(&'a InlineTypePathSegment<'a>);
430
431impl<'a> CodegenTypePathSegment<'a> {
432 pub fn display(&self) -> impl Display {
433 struct DisplaySegment<'a>(&'a InlineTypePathSegment<'a>);
434 impl Display for DisplaySegment<'_> {
435 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
436 use InlineTypePathSegment::*;
437 match self.0 {
438 Operation(name) => write!(f, "{}", AsPascalCase(clean(name))),
441 Parameter(name) => write!(f, "{}", AsPascalCase(clean(name))),
442 Request => f.write_str("Request"),
443 Response => f.write_str("Response"),
444 Field(StructFieldName::Name(name)) => {
445 write!(f, "{}", AsPascalCase(clean(name)))
446 }
447 Field(StructFieldName::Hint(StructFieldNameHint::Index(index))) => {
448 write!(f, "Variant{index}")
449 }
450 Field(StructFieldName::Hint(StructFieldNameHint::AdditionalProperties)) => {
451 f.write_str("AdditionalProperties")
452 }
453 MapValue => f.write_str("Value"),
454 ArrayItem => f.write_str("Item"),
455 Variant(index) => write!(f, "V{index}"),
456 Parent(index) => write!(f, "P{index}"),
457 TaggedVariant(name) => write!(f, "{}", AsPascalCase(clean(name))),
458 }
459 }
460 }
461 DisplaySegment(self.0)
462 }
463}
464
465fn clean(s: &str) -> String {
477 WordSegments::new(s)
478 .flat_map(|s| s.split(|c| !unicode_ident::is_xid_continue(c)))
479 .join("_")
480}
481
482#[cfg(test)]
483mod tests {
484 use super::*;
485
486 use pretty_assertions::assert_eq;
487 use syn::parse_quote;
488
489 #[test]
492 fn test_feature_from_name() {
493 let feature = CargoFeature::from_name("customers");
494 assert_eq!(feature.display().to_string(), "customers");
495 }
496
497 #[test]
498 fn test_feature_default() {
499 let feature = CargoFeature::Default;
500 assert_eq!(feature.display().to_string(), "default");
501
502 let feature = CargoFeature::from_name("default");
503 assert_eq!(feature, CargoFeature::Default);
504 }
505
506 #[test]
507 fn test_features_from_multiple_words() {
508 let feature = CargoFeature::from_name("foo_bar");
509 assert_eq!(feature.display().to_string(), "foo-bar");
510
511 let feature = CargoFeature::from_name("foo.bar");
512 assert_eq!(feature.display().to_string(), "foo-bar");
513
514 let feature = CargoFeature::from_name("fooBar");
515 assert_eq!(feature.display().to_string(), "foo-bar");
516
517 let feature = CargoFeature::from_name("FooBar");
518 assert_eq!(feature.display().to_string(), "foo-bar");
519 }
520
521 #[test]
524 fn test_codegen_ident_type() {
525 let ident = CodegenIdent::new("pet_store");
526 let usage = CodegenIdentUsage::Type(&ident);
527 let actual: syn::Ident = parse_quote!(#usage);
528 let expected: syn::Ident = parse_quote!(PetStore);
529 assert_eq!(actual, expected);
530 }
531
532 #[test]
533 fn test_codegen_ident_field() {
534 let ident = CodegenIdent::new("petStore");
535 let usage = CodegenIdentUsage::Field(&ident);
536 let actual: syn::Ident = parse_quote!(#usage);
537 let expected: syn::Ident = parse_quote!(pet_store);
538 assert_eq!(actual, expected);
539 }
540
541 #[test]
542 fn test_codegen_ident_module() {
543 let ident = CodegenIdent::new("MyModule");
544 let usage = CodegenIdentUsage::Module(&ident);
545 let actual: syn::Ident = parse_quote!(#usage);
546 let expected: syn::Ident = parse_quote!(my_module);
547 assert_eq!(actual, expected);
548 }
549
550 #[test]
551 fn test_codegen_ident_variant() {
552 let ident = CodegenIdent::new("http_error");
553 let usage = CodegenIdentUsage::Variant(&ident);
554 let actual: syn::Ident = parse_quote!(#usage);
555 let expected: syn::Ident = parse_quote!(HttpError);
556 assert_eq!(actual, expected);
557 }
558
559 #[test]
560 fn test_codegen_ident_param() {
561 let ident = CodegenIdent::new("userId");
562 let usage = CodegenIdentUsage::Param(&ident);
563 let actual: syn::Ident = parse_quote!(#usage);
564 let expected: syn::Ident = parse_quote!(user_id);
565 assert_eq!(actual, expected);
566 }
567
568 #[test]
569 fn test_codegen_ident_method() {
570 let ident = CodegenIdent::new("getUserById");
571 let usage = CodegenIdentUsage::Method(&ident);
572 let actual: syn::Ident = parse_quote!(#usage);
573 let expected: syn::Ident = parse_quote!(get_user_by_id);
574 assert_eq!(actual, expected);
575 }
576
577 #[test]
580 fn test_codegen_ident_handles_rust_keywords() {
581 let ident = CodegenIdent::new("type");
582 let usage = CodegenIdentUsage::Field(&ident);
583 let actual: syn::Ident = parse_quote!(#usage);
584 let expected: syn::Ident = parse_quote!(r#type);
585 assert_eq!(actual, expected);
586 }
587
588 #[test]
589 fn test_codegen_ident_handles_invalid_start_chars() {
590 let ident = CodegenIdent::new("123foo");
591 let usage = CodegenIdentUsage::Field(&ident);
592 let actual: syn::Ident = parse_quote!(#usage);
593 let expected: syn::Ident = parse_quote!(_123_foo);
594 assert_eq!(actual, expected);
595 }
596
597 #[test]
598 fn test_codegen_ident_handles_special_chars() {
599 let ident = CodegenIdent::new("foo-bar-baz");
600 let usage = CodegenIdentUsage::Field(&ident);
601 let actual: syn::Ident = parse_quote!(#usage);
602 let expected: syn::Ident = parse_quote!(foo_bar_baz);
603 assert_eq!(actual, expected);
604 }
605
606 #[test]
607 fn test_codegen_ident_handles_number_prefix() {
608 let ident = CodegenIdent::new("1099KStatus");
609
610 let usage = CodegenIdentUsage::Field(&ident);
611 let actual: syn::Ident = parse_quote!(#usage);
612 let expected: syn::Ident = parse_quote!(_1099_k_status);
613 assert_eq!(actual, expected);
614
615 let usage = CodegenIdentUsage::Type(&ident);
616 let actual: syn::Ident = parse_quote!(#usage);
617 let expected: syn::Ident = parse_quote!(_1099KStatus);
618 assert_eq!(actual, expected);
619 }
620
621 #[test]
624 fn test_untagged_variant_name_string() {
625 let variant_name =
626 CodegenUntaggedVariantName(UntaggedVariantNameHint::Primitive(PrimitiveType::String));
627 let actual: syn::Ident = parse_quote!(#variant_name);
628 let expected: syn::Ident = parse_quote!(String);
629 assert_eq!(actual, expected);
630 }
631
632 #[test]
633 fn test_untagged_variant_name_i32() {
634 let variant_name =
635 CodegenUntaggedVariantName(UntaggedVariantNameHint::Primitive(PrimitiveType::I32));
636 let actual: syn::Ident = parse_quote!(#variant_name);
637 let expected: syn::Ident = parse_quote!(I32);
638 assert_eq!(actual, expected);
639 }
640
641 #[test]
642 fn test_untagged_variant_name_i64() {
643 let variant_name =
644 CodegenUntaggedVariantName(UntaggedVariantNameHint::Primitive(PrimitiveType::I64));
645 let actual: syn::Ident = parse_quote!(#variant_name);
646 let expected: syn::Ident = parse_quote!(I64);
647 assert_eq!(actual, expected);
648 }
649
650 #[test]
651 fn test_untagged_variant_name_f32() {
652 let variant_name =
653 CodegenUntaggedVariantName(UntaggedVariantNameHint::Primitive(PrimitiveType::F32));
654 let actual: syn::Ident = parse_quote!(#variant_name);
655 let expected: syn::Ident = parse_quote!(F32);
656 assert_eq!(actual, expected);
657 }
658
659 #[test]
660 fn test_untagged_variant_name_f64() {
661 let variant_name =
662 CodegenUntaggedVariantName(UntaggedVariantNameHint::Primitive(PrimitiveType::F64));
663 let actual: syn::Ident = parse_quote!(#variant_name);
664 let expected: syn::Ident = parse_quote!(F64);
665 assert_eq!(actual, expected);
666 }
667
668 #[test]
669 fn test_untagged_variant_name_bool() {
670 let variant_name =
671 CodegenUntaggedVariantName(UntaggedVariantNameHint::Primitive(PrimitiveType::Bool));
672 let actual: syn::Ident = parse_quote!(#variant_name);
673 let expected: syn::Ident = parse_quote!(Bool);
674 assert_eq!(actual, expected);
675 }
676
677 #[test]
678 fn test_untagged_variant_name_datetime() {
679 let variant_name =
680 CodegenUntaggedVariantName(UntaggedVariantNameHint::Primitive(PrimitiveType::DateTime));
681 let actual: syn::Ident = parse_quote!(#variant_name);
682 let expected: syn::Ident = parse_quote!(DateTime);
683 assert_eq!(actual, expected);
684 }
685
686 #[test]
687 fn test_untagged_variant_name_date() {
688 let variant_name =
689 CodegenUntaggedVariantName(UntaggedVariantNameHint::Primitive(PrimitiveType::Date));
690 let actual: syn::Ident = parse_quote!(#variant_name);
691 let expected: syn::Ident = parse_quote!(Date);
692 assert_eq!(actual, expected);
693 }
694
695 #[test]
696 fn test_untagged_variant_name_url() {
697 let variant_name =
698 CodegenUntaggedVariantName(UntaggedVariantNameHint::Primitive(PrimitiveType::Url));
699 let actual: syn::Ident = parse_quote!(#variant_name);
700 let expected: syn::Ident = parse_quote!(Url);
701 assert_eq!(actual, expected);
702 }
703
704 #[test]
705 fn test_untagged_variant_name_uuid() {
706 let variant_name =
707 CodegenUntaggedVariantName(UntaggedVariantNameHint::Primitive(PrimitiveType::Uuid));
708 let actual: syn::Ident = parse_quote!(#variant_name);
709 let expected: syn::Ident = parse_quote!(Uuid);
710 assert_eq!(actual, expected);
711 }
712
713 #[test]
714 fn test_untagged_variant_name_bytes() {
715 let variant_name =
716 CodegenUntaggedVariantName(UntaggedVariantNameHint::Primitive(PrimitiveType::Bytes));
717 let actual: syn::Ident = parse_quote!(#variant_name);
718 let expected: syn::Ident = parse_quote!(Bytes);
719 assert_eq!(actual, expected);
720 }
721
722 #[test]
723 fn test_untagged_variant_name_index() {
724 let variant_name = CodegenUntaggedVariantName(UntaggedVariantNameHint::Index(0));
725 let actual: syn::Ident = parse_quote!(#variant_name);
726 let expected: syn::Ident = parse_quote!(V0);
727 assert_eq!(actual, expected);
728
729 let variant_name = CodegenUntaggedVariantName(UntaggedVariantNameHint::Index(42));
730 let actual: syn::Ident = parse_quote!(#variant_name);
731 let expected: syn::Ident = parse_quote!(V42);
732 assert_eq!(actual, expected);
733 }
734
735 #[test]
736 fn test_untagged_variant_name_array() {
737 let variant_name = CodegenUntaggedVariantName(UntaggedVariantNameHint::Array);
738 let actual: syn::Ident = parse_quote!(#variant_name);
739 let expected: syn::Ident = parse_quote!(Array);
740 assert_eq!(actual, expected);
741 }
742
743 #[test]
744 fn test_untagged_variant_name_map() {
745 let variant_name = CodegenUntaggedVariantName(UntaggedVariantNameHint::Map);
746 let actual: syn::Ident = parse_quote!(#variant_name);
747 let expected: syn::Ident = parse_quote!(Map);
748 assert_eq!(actual, expected);
749 }
750
751 #[test]
754 fn test_struct_field_name_index() {
755 let field_name = CodegenIdentRef::from_field_name_hint(StructFieldNameHint::Index(0));
756 let usage = CodegenIdentUsage::Field(&field_name);
757 let actual: syn::Ident = parse_quote!(#usage);
758 let expected: syn::Ident = parse_quote!(variant_0);
759 assert_eq!(actual, expected);
760
761 let field_name = CodegenIdentRef::from_field_name_hint(StructFieldNameHint::Index(5));
762 let usage = CodegenIdentUsage::Field(&field_name);
763 let actual: syn::Ident = parse_quote!(#usage);
764 let expected: syn::Ident = parse_quote!(variant_5);
765 assert_eq!(actual, expected);
766 }
767
768 #[test]
769 fn test_struct_field_name_additional_properties() {
770 let field_name =
771 CodegenIdentRef::from_field_name_hint(StructFieldNameHint::AdditionalProperties);
772 let usage = CodegenIdentUsage::Field(&field_name);
773 let actual: syn::Ident = parse_quote!(#usage);
774 let expected: syn::Ident = parse_quote!(additional_properties);
775 assert_eq!(actual, expected);
776 }
777
778 #[test]
781 fn test_clean() {
782 assert_eq!(clean("foo-bar"), "foo_bar");
783 assert_eq!(clean("foo.bar"), "foo_bar");
784 assert_eq!(clean("foo bar"), "foo_bar");
785 assert_eq!(clean("foo@bar"), "foo_bar");
786 assert_eq!(clean("foo#bar"), "foo_bar");
787 assert_eq!(clean("foo!bar"), "foo_bar");
788
789 assert_eq!(clean("foo_bar"), "foo_bar");
790 assert_eq!(clean("FooBar"), "Foo_Bar");
791 assert_eq!(clean("foo123"), "foo123");
792 assert_eq!(clean("_foo"), "foo");
793
794 assert_eq!(clean("_foo"), "foo");
795 assert_eq!(clean("__foo"), "foo");
796
797 assert_eq!(clean("123foo"), "123_foo");
799 assert_eq!(clean("9bar"), "9_bar");
800
801 assert_eq!(clean("café"), "café");
804 assert_eq!(clean("foo™bar"), "foo_bar");
805
806 assert_eq!(clean("foo---bar"), "foo_bar");
808 assert_eq!(clean("foo...bar"), "foo_bar");
809 }
810
811 #[test]
814 fn test_codegen_ident_scope_handles_empty() {
815 let unique = UniqueNames::new();
816 let mut scope = CodegenIdentScope::new(&unique);
817 let ident = scope.uniquify("");
818
819 let usage = CodegenIdentUsage::Field(&ident);
820 let actual: syn::Ident = parse_quote!(#usage);
821 let expected: syn::Ident = parse_quote!(_2);
822 assert_eq!(actual, expected);
823
824 let usage = CodegenIdentUsage::Type(&ident);
825 let actual: syn::Ident = parse_quote!(#usage);
826 let expected: syn::Ident = parse_quote!(_2);
827 assert_eq!(actual, expected);
828 }
829}