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, 'a>),
40 Inline(&'a InlineTypeView<'a, '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, 'a>) -> Self {
135 Self(CodegenTypeName::Schema(view))
136 }
137
138 #[inline]
139 pub fn for_inline(view: &'a InlineTypeView<'a, '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 pub fn from_variant_name_hint(hint: UntaggedVariantNameHint) -> Cow<'static, Self> {
247 use {PrimitiveType::*, UntaggedVariantNameHint::*};
248 match hint {
249 Primitive(String) => Cow::Borrowed(Self::new("String")),
250 Primitive(I8) => Cow::Borrowed(Self::new("I8")),
251 Primitive(U8) => Cow::Borrowed(Self::new("U8")),
252 Primitive(I16) => Cow::Borrowed(Self::new("I16")),
253 Primitive(U16) => Cow::Borrowed(Self::new("U16")),
254 Primitive(I32) => Cow::Borrowed(Self::new("I32")),
255 Primitive(U32) => Cow::Borrowed(Self::new("U32")),
256 Primitive(I64) => Cow::Borrowed(Self::new("I64")),
257 Primitive(U64) => Cow::Borrowed(Self::new("U64")),
258 Primitive(F32) => Cow::Borrowed(Self::new("F32")),
259 Primitive(F64) => Cow::Borrowed(Self::new("F64")),
260 Primitive(Bool) => Cow::Borrowed(Self::new("Bool")),
261 Primitive(DateTime) => Cow::Borrowed(Self::new("DateTime")),
262 Primitive(UnixTime) => Cow::Borrowed(Self::new("UnixTime")),
263 Primitive(Date) => Cow::Borrowed(Self::new("Date")),
264 Primitive(Url) => Cow::Borrowed(Self::new("Url")),
265 Primitive(Uuid) => Cow::Borrowed(Self::new("Uuid")),
266 Primitive(Bytes) => Cow::Borrowed(Self::new("Bytes")),
267 Primitive(Binary) => Cow::Borrowed(Self::new("Binary")),
268 Array => Cow::Borrowed(Self::new("Array")),
269 Map => Cow::Borrowed(Self::new("Map")),
270 Index(index) => Cow::Owned(CodegenIdent(format!("V{index}"))),
271 }
272 }
273}
274
275impl ToOwned for CodegenIdentRef {
276 type Owned = CodegenIdent;
277
278 fn to_owned(&self) -> Self::Owned {
279 CodegenIdent(self.0.to_owned())
280 }
281}
282
283#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
289pub enum CargoFeature {
290 #[default]
291 Default,
292 Named(CodegenIdent),
293}
294
295impl CargoFeature {
296 #[inline]
297 pub fn from_name(name: &str) -> Self {
298 match name {
299 "default" => Self::Default,
301
302 name => Self::Named(CodegenIdent::new(name)),
306 }
307 }
308
309 #[inline]
310 pub fn as_ident(&self) -> &CodegenIdentRef {
311 match self {
312 Self::Named(name) => name,
313 Self::Default => CodegenIdentRef::new("default"),
314 }
315 }
316
317 #[inline]
318 pub fn display(&self) -> impl Display {
319 match self {
320 Self::Named(name) => AsKebabCase(name.0.as_str()),
321 Self::Default => AsKebabCase("default"),
322 }
323 }
324}
325
326#[derive(Clone, Copy, Debug)]
338pub enum CodegenIdentUsage<'a> {
339 Module(&'a CodegenIdentRef),
340 Type(&'a CodegenIdentRef),
341 Field(&'a CodegenIdentRef),
342 Variant(&'a CodegenIdentRef),
343 Param(&'a CodegenIdentRef),
344 Method(&'a CodegenIdentRef),
345}
346
347impl CodegenIdentUsage<'_> {
348 pub fn display(self) -> impl Display {
354 struct DisplayUsage<'a>(CodegenIdentUsage<'a>);
355 impl Display for DisplayUsage<'_> {
356 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
357 use CodegenIdentUsage::*;
358 match self.0 {
359 Module(name) | Field(name) | Param(name) | Method(name) => {
360 if name.0.starts_with(unicode_ident::is_xid_start) {
361 write!(f, "{}", AsSnekCase(&name.0))
362 } else {
363 write!(f, "_{}", AsSnekCase(&name.0))
367 }
368 }
369 Type(name) | Variant(name) => {
370 if name.0.starts_with(unicode_ident::is_xid_start) {
371 write!(f, "{}", AsPascalCase(&name.0))
372 } else {
373 write!(f, "_{}", AsPascalCase(&name.0))
374 }
375 }
376 }
377 }
378 }
379 DisplayUsage(self)
380 }
381}
382
383impl IdentFragment for CodegenIdentUsage<'_> {
384 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
385 write!(f, "{}", self.display())
386 }
387}
388
389impl ToTokens for CodegenIdentUsage<'_> {
390 fn to_tokens(&self, tokens: &mut TokenStream) {
391 let s = self.display().to_string();
392 let ident = syn::parse_str(&s).unwrap_or_else(|_| Ident::new_raw(&s, Span::call_site()));
393 tokens.append(ident);
394 }
395}
396
397#[derive(Debug)]
399pub struct CodegenIdentScope<'a>(UniqueNamesScope<'a>);
400
401impl<'a> CodegenIdentScope<'a> {
402 pub fn new(arena: &'a UniqueNames) -> Self {
404 Self::with_reserved(arena, &[])
405 }
406
407 pub fn with_reserved(arena: &'a UniqueNames, reserved: &[&str]) -> Self {
410 Self(arena.scope_with_reserved(itertools::chain!(
411 reserved.iter().copied(),
412 KEYWORDS.iter().copied(),
413 std::iter::once("")
414 )))
415 }
416
417 pub fn uniquify(&mut self, name: &str) -> CodegenIdent {
420 CodegenIdent(self.0.uniquify(&clean(name)).into_owned())
421 }
422
423 pub fn uniquify_ident(&mut self, ident: &CodegenIdentRef) -> CodegenIdent {
425 CodegenIdent(self.0.uniquify(&ident.0).into_owned())
426 }
427}
428
429#[derive(Clone, Copy, Debug)]
430pub struct CodegenTypePathSegment<'a>(&'a InlineTypePathSegment<'a>);
431
432impl<'a> CodegenTypePathSegment<'a> {
433 pub fn display(&self) -> impl Display {
434 struct DisplaySegment<'a>(&'a InlineTypePathSegment<'a>);
435 impl Display for DisplaySegment<'_> {
436 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
437 use InlineTypePathSegment::*;
438 match self.0 {
439 Operation(name) => write!(f, "{}", AsPascalCase(clean(name))),
442 Parameter(name) => write!(f, "{}", AsPascalCase(clean(name))),
443 Request => f.write_str("Request"),
444 Response => f.write_str("Response"),
445 Field(StructFieldName::Name(name)) => {
446 write!(f, "{}", AsPascalCase(clean(name)))
447 }
448 Field(StructFieldName::Hint(StructFieldNameHint::Index(index))) => {
449 write!(f, "Variant{index}")
450 }
451 Field(StructFieldName::Hint(StructFieldNameHint::AdditionalProperties)) => {
452 f.write_str("AdditionalProperties")
453 }
454 MapValue => f.write_str("Value"),
455 ArrayItem => f.write_str("Item"),
456 Variant(index) => write!(f, "V{index}"),
457 Parent(index) => write!(f, "P{index}"),
458 TaggedVariant(name) => write!(f, "{}", AsPascalCase(clean(name))),
459 }
460 }
461 }
462 DisplaySegment(self.0)
463 }
464}
465
466fn clean(s: &str) -> String {
478 WordSegments::new(s)
479 .flat_map(|s| s.split(|c| !unicode_ident::is_xid_continue(c)))
480 .join("_")
481}
482
483#[cfg(test)]
484mod tests {
485 use super::*;
486
487 use pretty_assertions::assert_eq;
488 use syn::parse_quote;
489
490 #[test]
493 fn test_feature_from_name() {
494 let feature = CargoFeature::from_name("customers");
495 assert_eq!(feature.display().to_string(), "customers");
496 }
497
498 #[test]
499 fn test_feature_default() {
500 let feature = CargoFeature::Default;
501 assert_eq!(feature.display().to_string(), "default");
502
503 let feature = CargoFeature::from_name("default");
504 assert_eq!(feature, CargoFeature::Default);
505 }
506
507 #[test]
508 fn test_features_from_multiple_words() {
509 let feature = CargoFeature::from_name("foo_bar");
510 assert_eq!(feature.display().to_string(), "foo-bar");
511
512 let feature = CargoFeature::from_name("foo.bar");
513 assert_eq!(feature.display().to_string(), "foo-bar");
514
515 let feature = CargoFeature::from_name("fooBar");
516 assert_eq!(feature.display().to_string(), "foo-bar");
517
518 let feature = CargoFeature::from_name("FooBar");
519 assert_eq!(feature.display().to_string(), "foo-bar");
520 }
521
522 #[test]
525 fn test_codegen_ident_type() {
526 let ident = CodegenIdent::new("pet_store");
527 let usage = CodegenIdentUsage::Type(&ident);
528 let actual: syn::Ident = parse_quote!(#usage);
529 let expected: syn::Ident = parse_quote!(PetStore);
530 assert_eq!(actual, expected);
531 }
532
533 #[test]
534 fn test_codegen_ident_field() {
535 let ident = CodegenIdent::new("petStore");
536 let usage = CodegenIdentUsage::Field(&ident);
537 let actual: syn::Ident = parse_quote!(#usage);
538 let expected: syn::Ident = parse_quote!(pet_store);
539 assert_eq!(actual, expected);
540 }
541
542 #[test]
543 fn test_codegen_ident_module() {
544 let ident = CodegenIdent::new("MyModule");
545 let usage = CodegenIdentUsage::Module(&ident);
546 let actual: syn::Ident = parse_quote!(#usage);
547 let expected: syn::Ident = parse_quote!(my_module);
548 assert_eq!(actual, expected);
549 }
550
551 #[test]
552 fn test_codegen_ident_variant() {
553 let ident = CodegenIdent::new("http_error");
554 let usage = CodegenIdentUsage::Variant(&ident);
555 let actual: syn::Ident = parse_quote!(#usage);
556 let expected: syn::Ident = parse_quote!(HttpError);
557 assert_eq!(actual, expected);
558 }
559
560 #[test]
561 fn test_codegen_ident_param() {
562 let ident = CodegenIdent::new("userId");
563 let usage = CodegenIdentUsage::Param(&ident);
564 let actual: syn::Ident = parse_quote!(#usage);
565 let expected: syn::Ident = parse_quote!(user_id);
566 assert_eq!(actual, expected);
567 }
568
569 #[test]
570 fn test_codegen_ident_method() {
571 let ident = CodegenIdent::new("getUserById");
572 let usage = CodegenIdentUsage::Method(&ident);
573 let actual: syn::Ident = parse_quote!(#usage);
574 let expected: syn::Ident = parse_quote!(get_user_by_id);
575 assert_eq!(actual, expected);
576 }
577
578 #[test]
581 fn test_codegen_ident_handles_rust_keywords() {
582 let ident = CodegenIdent::new("type");
583 let usage = CodegenIdentUsage::Field(&ident);
584 let actual: syn::Ident = parse_quote!(#usage);
585 let expected: syn::Ident = parse_quote!(r#type);
586 assert_eq!(actual, expected);
587 }
588
589 #[test]
590 fn test_codegen_ident_handles_invalid_start_chars() {
591 let ident = CodegenIdent::new("123foo");
592 let usage = CodegenIdentUsage::Field(&ident);
593 let actual: syn::Ident = parse_quote!(#usage);
594 let expected: syn::Ident = parse_quote!(_123_foo);
595 assert_eq!(actual, expected);
596 }
597
598 #[test]
599 fn test_codegen_ident_handles_special_chars() {
600 let ident = CodegenIdent::new("foo-bar-baz");
601 let usage = CodegenIdentUsage::Field(&ident);
602 let actual: syn::Ident = parse_quote!(#usage);
603 let expected: syn::Ident = parse_quote!(foo_bar_baz);
604 assert_eq!(actual, expected);
605 }
606
607 #[test]
608 fn test_codegen_ident_handles_number_prefix() {
609 let ident = CodegenIdent::new("1099KStatus");
610
611 let usage = CodegenIdentUsage::Field(&ident);
612 let actual: syn::Ident = parse_quote!(#usage);
613 let expected: syn::Ident = parse_quote!(_1099_k_status);
614 assert_eq!(actual, expected);
615
616 let usage = CodegenIdentUsage::Type(&ident);
617 let actual: syn::Ident = parse_quote!(#usage);
618 let expected: syn::Ident = parse_quote!(_1099KStatus);
619 assert_eq!(actual, expected);
620 }
621
622 #[test]
625 fn test_untagged_variant_name_index() {
626 let name = CodegenIdentRef::from_variant_name_hint(UntaggedVariantNameHint::Index(0));
627 assert_eq!(&*name, CodegenIdentRef::new("V0"));
628
629 let name = CodegenIdentRef::from_variant_name_hint(UntaggedVariantNameHint::Index(42));
630 assert_eq!(&*name, CodegenIdentRef::new("V42"));
631 }
632
633 #[test]
636 fn test_struct_field_name_index() {
637 let field_name = CodegenIdentRef::from_field_name_hint(StructFieldNameHint::Index(0));
638 let usage = CodegenIdentUsage::Field(&field_name);
639 let actual: syn::Ident = parse_quote!(#usage);
640 let expected: syn::Ident = parse_quote!(variant_0);
641 assert_eq!(actual, expected);
642
643 let field_name = CodegenIdentRef::from_field_name_hint(StructFieldNameHint::Index(5));
644 let usage = CodegenIdentUsage::Field(&field_name);
645 let actual: syn::Ident = parse_quote!(#usage);
646 let expected: syn::Ident = parse_quote!(variant_5);
647 assert_eq!(actual, expected);
648 }
649
650 #[test]
651 fn test_struct_field_name_additional_properties() {
652 let field_name =
653 CodegenIdentRef::from_field_name_hint(StructFieldNameHint::AdditionalProperties);
654 let usage = CodegenIdentUsage::Field(&field_name);
655 let actual: syn::Ident = parse_quote!(#usage);
656 let expected: syn::Ident = parse_quote!(additional_properties);
657 assert_eq!(actual, expected);
658 }
659
660 #[test]
663 fn test_clean() {
664 assert_eq!(clean("foo-bar"), "foo_bar");
665 assert_eq!(clean("foo.bar"), "foo_bar");
666 assert_eq!(clean("foo bar"), "foo_bar");
667 assert_eq!(clean("foo@bar"), "foo_bar");
668 assert_eq!(clean("foo#bar"), "foo_bar");
669 assert_eq!(clean("foo!bar"), "foo_bar");
670
671 assert_eq!(clean("foo_bar"), "foo_bar");
672 assert_eq!(clean("FooBar"), "Foo_Bar");
673 assert_eq!(clean("foo123"), "foo123");
674 assert_eq!(clean("_foo"), "foo");
675
676 assert_eq!(clean("_foo"), "foo");
677 assert_eq!(clean("__foo"), "foo");
678
679 assert_eq!(clean("123foo"), "123_foo");
681 assert_eq!(clean("9bar"), "9_bar");
682
683 assert_eq!(clean("café"), "café");
686 assert_eq!(clean("foo™bar"), "foo_bar");
687
688 assert_eq!(clean("foo---bar"), "foo_bar");
690 assert_eq!(clean("foo...bar"), "foo_bar");
691 }
692
693 #[test]
696 fn test_codegen_ident_scope_handles_empty() {
697 let unique = UniqueNames::new();
698 let mut scope = CodegenIdentScope::new(&unique);
699 let ident = scope.uniquify("");
700
701 let usage = CodegenIdentUsage::Field(&ident);
702 let actual: syn::Ident = parse_quote!(#usage);
703 let expected: syn::Ident = parse_quote!(_2);
704 assert_eq!(actual, expected);
705
706 let usage = CodegenIdentUsage::Type(&ident);
707 let actual: syn::Ident = parse_quote!(#usage);
708 let expected: syn::Ident = parse_quote!(_2);
709 assert_eq!(actual, expected);
710 }
711}