1extern crate alloc;
24
25use alloc::collections::{BTreeMap, BTreeSet};
26use alloc::string::String;
27use alloc::vec::Vec;
28use core::fmt::Write;
29
30use facet_core::{Def, Facet, Field, Shape, StructKind, Type, UserType};
31
32fn is_python_keyword(name: &str) -> bool {
34 const KEYWORDS: &[&str] = &[
36 "False", "None", "True", "and", "as", "assert", "async", "await", "break", "class",
37 "continue", "def", "del", "elif", "else", "except", "finally", "for", "from", "global",
38 "if", "import", "in", "is", "lambda", "nonlocal", "not", "or", "pass", "raise", "return",
39 "try", "while", "with", "yield",
40 ];
41 KEYWORDS.binary_search(&name).is_ok()
42}
43
44struct TypedDictField<'a> {
46 name: &'a str,
47 type_string: String,
48 required: bool,
49 doc: &'a [&'a str],
50}
51
52impl<'a> TypedDictField<'a> {
53 fn new(name: &'a str, type_string: String, required: bool, doc: &'a [&'a str]) -> Self {
54 Self {
55 name,
56 type_string,
57 required,
58 doc,
59 }
60 }
61
62 fn full_type_string(&self) -> String {
64 if self.required {
65 format!("Required[{}]", self.type_string)
66 } else {
67 self.type_string.clone()
68 }
69 }
70}
71
72fn has_reserved_keyword_field(fields: &[TypedDictField]) -> bool {
74 fields.iter().any(|f| is_python_keyword(f.name))
75}
76
77fn write_typed_dict_functional(output: &mut String, class_name: &str, fields: &[TypedDictField]) {
79 writeln!(output, "{} = TypedDict(", class_name).unwrap();
80 writeln!(output, " \"{}\",", class_name).unwrap();
81 output.push_str(" {");
82
83 let mut first = true;
84 for field in fields {
85 if !first {
86 output.push_str(", ");
87 }
88 first = false;
89
90 write!(output, "\"{}\": {}", field.name, field.full_type_string()).unwrap();
91 }
92
93 output.push_str("},\n");
94 output.push_str(" total=False,\n");
95 output.push(')');
96}
97
98fn write_typed_dict_class(output: &mut String, class_name: &str, fields: &[TypedDictField]) {
100 writeln!(output, "class {}(TypedDict, total=False):", class_name).unwrap();
101
102 if fields.is_empty() {
103 output.push_str(" pass");
104 return;
105 }
106
107 for field in fields {
108 for line in field.doc {
110 output.push_str(" #");
111 output.push_str(line);
112 output.push('\n');
113 }
114
115 writeln!(output, " {}: {}", field.name, field.full_type_string()).unwrap();
116 }
117}
118
119fn write_typed_dict(output: &mut String, class_name: &str, fields: &[TypedDictField]) {
121 if has_reserved_keyword_field(fields) {
122 write_typed_dict_functional(output, class_name, fields);
123 } else {
124 write_typed_dict_class(output, class_name, fields);
125 }
126}
127
128pub fn to_python<T: Facet<'static>>(write_imports: bool) -> String {
130 let mut generator = PythonGenerator::new();
131 generator.add_shape(T::SHAPE);
132 generator.finish(write_imports)
133}
134
135pub struct PythonGenerator {
137 generated: BTreeMap<String, String>,
139 queue: Vec<&'static Shape>,
141 imports: BTreeSet<&'static str>,
143}
144
145impl Default for PythonGenerator {
146 fn default() -> Self {
147 Self::new()
148 }
149}
150
151impl PythonGenerator {
152 pub const fn new() -> Self {
154 Self {
155 generated: BTreeMap::new(),
156 queue: Vec::new(),
157 imports: BTreeSet::new(),
158 }
159 }
160
161 pub fn add_type<T: Facet<'static>>(&mut self) {
163 self.add_shape(T::SHAPE);
164 }
165
166 pub fn add_shape(&mut self, shape: &'static Shape) {
168 if !self.generated.contains_key(shape.type_identifier) {
169 self.queue.push(shape);
170 }
171 }
172
173 pub fn finish(mut self, write_imports: bool) -> String {
175 while let Some(shape) = self.queue.pop() {
177 if self.generated.contains_key(shape.type_identifier) {
178 continue;
179 }
180 self.generated
182 .insert(shape.type_identifier.to_string(), String::new());
183 self.generate_shape(shape);
184 }
185
186 let mut output = String::new();
189
190 if write_imports {
192 writeln!(output, "from __future__ import annotations").unwrap();
195
196 if !self.imports.is_empty() {
197 let imports: Vec<&str> = self.imports.iter().copied().collect();
198 writeln!(output, "from typing import {}", imports.join(", ")).unwrap();
199 }
200 output.push('\n');
201 }
202
203 for code in self.generated.values() {
204 output.push_str(code);
205 }
206 output
207 }
208
209 fn generate_shape(&mut self, shape: &'static Shape) {
210 let mut output = String::new();
211
212 if let Some(inner) = shape.inner {
214 self.add_shape(inner);
215 let inner_type = self.type_for_shape(inner, None);
216 write_doc_comment(&mut output, shape.doc);
217 writeln!(output, "type {} = {}", shape.type_identifier, inner_type).unwrap();
218 output.push('\n');
219 self.generated
220 .insert(shape.type_identifier.to_string(), output);
221 return;
222 }
223
224 match &shape.ty {
225 Type::User(UserType::Struct(st)) => {
226 self.generate_struct(&mut output, shape, st.fields, st.kind);
227 }
228 Type::User(UserType::Enum(en)) => {
229 self.generate_enum(&mut output, shape, en);
230 }
231 _ => {
232 let type_str = self.type_for_shape(shape, None);
234 write_doc_comment(&mut output, shape.doc);
235 writeln!(output, "type {} = {}", shape.type_identifier, type_str).unwrap();
236 output.push('\n');
237 }
238 }
239
240 self.generated
241 .insert(shape.type_identifier.to_string(), output);
242 }
243
244 fn generate_struct(
245 &mut self,
246 output: &mut String,
247 shape: &'static Shape,
248 fields: &'static [Field],
249 kind: StructKind,
250 ) {
251 match kind {
252 StructKind::Unit => {
253 write_doc_comment(output, shape.doc);
254 writeln!(output, "{} = None", shape.type_identifier).unwrap();
255 }
256 StructKind::TupleStruct | StructKind::Tuple if fields.is_empty() => {
257 write_doc_comment(output, shape.doc);
259 writeln!(output, "{} = None", shape.type_identifier).unwrap();
260 }
261 StructKind::TupleStruct if fields.len() == 1 => {
262 let inner_type = self.type_for_shape(fields[0].shape.get(), None);
263 write_doc_comment(output, shape.doc);
264 writeln!(output, "{} = {}", shape.type_identifier, inner_type).unwrap();
265 }
266 StructKind::TupleStruct | StructKind::Tuple => {
267 let types: Vec<String> = fields
268 .iter()
269 .map(|f| self.type_for_shape(f.shape.get(), None))
270 .collect();
271 write_doc_comment(output, shape.doc);
272 writeln!(
273 output,
274 "{} = tuple[{}]",
275 shape.type_identifier,
276 types.join(", ")
277 )
278 .unwrap();
279 }
280 StructKind::Struct => {
281 self.generate_typed_dict(output, shape, fields);
282 }
283 }
284 output.push('\n');
285 }
286
287 fn generate_typed_dict(
289 &mut self,
290 output: &mut String,
291 shape: &'static Shape,
292 fields: &'static [Field],
293 ) {
294 self.imports.insert("TypedDict");
295
296 let visible_fields: Vec<_> = fields
297 .iter()
298 .filter(|f| !f.flags.contains(facet_core::FieldFlags::SKIP))
299 .collect();
300
301 let needs_functional = visible_fields
303 .iter()
304 .any(|f| is_python_keyword(f.effective_name()));
305 let quote_after: Option<&str> = if needs_functional {
306 Some(shape.type_identifier)
307 } else {
308 None
309 };
310
311 let typed_dict_fields: Vec<_> = visible_fields
313 .iter()
314 .map(|f| {
315 let (type_string, required) = self.field_type_info(f, quote_after);
316 TypedDictField::new(f.effective_name(), type_string, required, f.doc)
317 })
318 .collect();
319
320 if typed_dict_fields.iter().any(|f| f.required) {
322 self.imports.insert("Required");
323 }
324
325 write_doc_comment(output, shape.doc);
326 write_typed_dict(output, shape.type_identifier, &typed_dict_fields);
327 }
328
329 fn field_type_info(&mut self, field: &Field, quote_after: Option<&str>) -> (String, bool) {
331 if let Def::Option(opt) = &field.shape.get().def {
332 (self.type_for_shape(opt.t, quote_after), false)
333 } else {
334 (self.type_for_shape(field.shape.get(), quote_after), true)
335 }
336 }
337
338 fn generate_enum(
339 &mut self,
340 output: &mut String,
341 shape: &'static Shape,
342 enum_type: &facet_core::EnumType,
343 ) {
344 let all_unit = enum_type
345 .variants
346 .iter()
347 .all(|v| matches!(v.data.kind, StructKind::Unit));
348
349 write_doc_comment(output, shape.doc);
350
351 if all_unit {
352 self.generate_enum_unit_variants(output, shape, enum_type);
353 } else {
354 self.generate_enum_with_data(output, shape, enum_type);
355 }
356 output.push('\n');
357 }
358
359 fn generate_enum_unit_variants(
361 &mut self,
362 output: &mut String,
363 shape: &'static Shape,
364 enum_type: &facet_core::EnumType,
365 ) {
366 self.imports.insert("Literal");
367
368 let variants: Vec<String> = enum_type
369 .variants
370 .iter()
371 .map(|v| format!("Literal[\"{}\"]", v.effective_name()))
372 .collect();
373
374 writeln!(
375 output,
376 "type {} = {}",
377 shape.type_identifier,
378 variants.join(" | ")
379 )
380 .unwrap();
381 }
382
383 fn generate_enum_with_data(
385 &mut self,
386 output: &mut String,
387 shape: &'static Shape,
388 enum_type: &facet_core::EnumType,
389 ) {
390 let mut variant_class_names = Vec::new();
391
392 for variant in enum_type.variants {
393 let variant_type_name = self.generate_enum_variant(variant);
394 variant_class_names.push(variant_type_name);
395 }
396
397 writeln!(
398 output,
399 "type {} = {}",
400 shape.type_identifier,
401 variant_class_names.join(" | ")
402 )
403 .unwrap();
404 }
405
406 fn generate_enum_variant(&mut self, variant: &facet_core::Variant) -> String {
408 let variant_name = variant.effective_name();
409 let pascal_variant_name = to_pascal_case(variant_name);
410
411 match variant.data.kind {
412 StructKind::Unit => {
413 self.imports.insert("Literal");
414 format!("Literal[\"{}\"]", variant_name)
415 }
416 StructKind::TupleStruct if variant.data.fields.len() == 1 => {
417 self.generate_newtype_variant(variant_name, &pascal_variant_name, variant);
418 pascal_variant_name.to_string()
419 }
420 StructKind::TupleStruct => {
421 self.generate_tuple_variant(variant_name, &pascal_variant_name, variant);
422 pascal_variant_name.to_string()
423 }
424 _ => {
425 self.generate_struct_variant(variant_name, &pascal_variant_name, variant);
426 pascal_variant_name.to_string()
427 }
428 }
429 }
430
431 fn generate_newtype_variant(
433 &mut self,
434 variant_name: &str,
435 pascal_variant_name: &str,
436 variant: &facet_core::Variant,
437 ) {
438 self.imports.insert("TypedDict");
439 self.imports.insert("Required");
440
441 let quote_after: Option<&str> = if is_python_keyword(variant_name) {
443 Some(pascal_variant_name)
444 } else {
445 None
446 };
447
448 let inner_type = self.type_for_shape(variant.data.fields[0].shape.get(), quote_after);
449
450 let fields = [TypedDictField::new(variant_name, inner_type, true, &[])];
451
452 let mut output = String::new();
453 write_typed_dict(&mut output, pascal_variant_name, &fields);
454 output.push('\n');
455
456 self.generated
457 .insert(pascal_variant_name.to_string(), output);
458 }
459
460 fn generate_tuple_variant(
462 &mut self,
463 variant_name: &str,
464 pascal_variant_name: &str,
465 variant: &facet_core::Variant,
466 ) {
467 self.imports.insert("TypedDict");
468 self.imports.insert("Required");
469
470 let quote_after: Option<&str> = if is_python_keyword(variant_name) {
472 Some(pascal_variant_name)
473 } else {
474 None
475 };
476
477 let types: Vec<String> = variant
478 .data
479 .fields
480 .iter()
481 .map(|f| self.type_for_shape(f.shape.get(), quote_after))
482 .collect();
483
484 let inner_type = format!("tuple[{}]", types.join(", "));
488
489 let fields = [TypedDictField::new(variant_name, inner_type, true, &[])];
490
491 let mut output = String::new();
492 write_typed_dict(&mut output, pascal_variant_name, &fields);
493 output.push('\n');
494
495 self.generated
496 .insert(pascal_variant_name.to_string(), output);
497 }
498
499 fn generate_struct_variant(
501 &mut self,
502 variant_name: &str,
503 pascal_variant_name: &str,
504 variant: &facet_core::Variant,
505 ) {
506 self.imports.insert("TypedDict");
507 self.imports.insert("Required");
508
509 let data_class_name = format!("{}Data", pascal_variant_name);
510
511 let needs_functional = variant
513 .data
514 .fields
515 .iter()
516 .any(|f| is_python_keyword(f.effective_name()));
517 let quote_after: Option<&str> = if needs_functional {
518 Some(&data_class_name)
519 } else {
520 None
521 };
522
523 let data_fields: Vec<_> = variant
525 .data
526 .fields
527 .iter()
528 .map(|field| {
529 let field_type = self.type_for_shape(field.shape.get(), quote_after);
530 TypedDictField::new(field.effective_name(), field_type, true, &[])
531 })
532 .collect();
533
534 let mut data_output = String::new();
535 write_typed_dict(&mut data_output, &data_class_name, &data_fields);
536 data_output.push('\n');
537 self.generated.insert(data_class_name.clone(), data_output);
538
539 let wrapper_type_str =
541 if is_python_keyword(variant_name) && data_class_name.as_str() > pascal_variant_name {
542 format!("\"{}\"", data_class_name)
543 } else {
544 data_class_name.clone()
545 };
546 let wrapper_fields = [TypedDictField::new(
547 variant_name,
548 wrapper_type_str,
549 true,
550 &[],
551 )];
552
553 let mut wrapper_output = String::new();
554 write_typed_dict(&mut wrapper_output, pascal_variant_name, &wrapper_fields);
555 wrapper_output.push('\n');
556
557 self.generated
558 .insert(pascal_variant_name.to_string(), wrapper_output);
559 }
560
561 fn type_for_shape(&mut self, shape: &'static Shape, quote_after: Option<&str>) -> String {
564 match &shape.def {
566 Def::Scalar => self.scalar_type(shape),
567 Def::Option(opt) => {
568 format!("{} | None", self.type_for_shape(opt.t, quote_after))
569 }
570 Def::List(list) => {
571 format!("list[{}]", self.type_for_shape(list.t, quote_after))
572 }
573 Def::Array(arr) => {
574 format!("list[{}]", self.type_for_shape(arr.t, quote_after))
575 }
576 Def::Set(set) => {
577 format!("list[{}]", self.type_for_shape(set.t, quote_after))
578 }
579 Def::Map(map) => {
580 format!(
581 "dict[{}, {}]",
582 self.type_for_shape(map.k, quote_after),
583 self.type_for_shape(map.v, quote_after)
584 )
585 }
586 Def::Pointer(ptr) => match ptr.pointee {
587 Some(pointee) => self.type_for_shape(pointee, quote_after),
588 None => {
589 self.imports.insert("Any");
590 "Any".to_string()
591 }
592 },
593 Def::Undefined => {
594 match &shape.ty {
596 Type::User(UserType::Struct(st)) => {
597 if st.kind == StructKind::Tuple {
600 let types: Vec<String> = st
601 .fields
602 .iter()
603 .map(|f| self.type_for_shape(f.shape.get(), quote_after))
604 .collect();
605 format!("tuple[{}]", types.join(", "))
606 } else {
607 self.add_shape(shape);
608 self.maybe_quote(shape.type_identifier, quote_after)
609 }
610 }
611 Type::User(UserType::Enum(_)) => {
612 self.add_shape(shape);
613 self.maybe_quote(shape.type_identifier, quote_after)
614 }
615 _ => self.inner_type_or_any(shape, quote_after),
616 }
617 }
618 _ => self.inner_type_or_any(shape, quote_after),
619 }
620 }
621
622 fn maybe_quote(&self, name: &str, quote_after: Option<&str>) -> String {
624 if let Some(after) = quote_after
625 && name > after
626 {
627 return format!("\"{}\"", name);
628 }
629 name.to_string()
630 }
631
632 fn inner_type_or_any(&mut self, shape: &'static Shape, quote_after: Option<&str>) -> String {
634 match shape.inner {
635 Some(inner) => self.type_for_shape(inner, quote_after),
636 None => {
637 self.imports.insert("Any");
638 "Any".to_string()
639 }
640 }
641 }
642
643 fn scalar_type(&mut self, shape: &'static Shape) -> String {
645 match shape.type_identifier {
646 "String" | "str" | "&str" | "Cow" => "str".to_string(),
648
649 "bool" => "bool".to_string(),
651
652 "u8" | "u16" | "u32" | "u64" | "u128" | "usize" | "i8" | "i16" | "i32" | "i64"
654 | "i128" | "isize" => "int".to_string(),
655
656 "f32" | "f64" => "float".to_string(),
658
659 "char" => "str".to_string(),
661
662 _ => {
664 self.imports.insert("Any");
665 "Any".to_string()
666 }
667 }
668 }
669}
670
671fn write_doc_comment(output: &mut String, doc: &[&str]) {
673 for line in doc {
674 output.push('#');
675 output.push_str(line);
676 output.push('\n');
677 }
678}
679
680fn to_pascal_case(s: &str) -> String {
682 let mut result = String::new();
683 let mut capitalize_next = true;
684
685 for c in s.chars() {
686 if c == '_' || c == '-' {
687 capitalize_next = true;
688 } else if capitalize_next {
689 result.push(c.to_ascii_uppercase());
690 capitalize_next = false;
691 } else {
692 result.push(c);
693 }
694 }
695
696 result
697}
698
699#[cfg(test)]
700mod tests {
701 use super::*;
702 use facet::Facet;
703
704 #[test]
705 fn test_simple_struct() {
706 #[derive(Facet)]
707 struct User {
708 name: String,
709 age: u32,
710 }
711
712 let py = to_python::<User>(false);
713 insta::assert_snapshot!(py);
714 }
715
716 #[test]
717 fn test_optional_field() {
718 #[derive(Facet)]
719 struct Config {
720 required: String,
721 optional: Option<String>,
722 }
723
724 let py = to_python::<Config>(false);
725 insta::assert_snapshot!(py);
726 }
727
728 #[test]
729 fn test_simple_enum() {
730 #[derive(Facet)]
731 #[repr(u8)]
732 enum Status {
733 Active,
734 Inactive,
735 Pending,
736 }
737
738 let py = to_python::<Status>(false);
739 insta::assert_snapshot!(py);
740 }
741
742 #[test]
743 fn test_vec() {
744 #[derive(Facet)]
745 struct Data {
746 items: Vec<String>,
747 }
748
749 let py = to_python::<Data>(false);
750 insta::assert_snapshot!(py);
751 }
752
753 #[test]
754 fn test_nested_types() {
755 #[derive(Facet)]
756 struct Inner {
757 value: i32,
758 }
759
760 #[derive(Facet)]
761 struct Outer {
762 inner: Inner,
763 name: String,
764 }
765
766 let py = to_python::<Outer>(false);
767 insta::assert_snapshot!(py);
768 }
769
770 #[test]
771 fn test_enum_rename_all_snake_case() {
772 #[derive(Facet)]
773 #[facet(rename_all = "snake_case")]
774 #[repr(u8)]
775 enum ValidationErrorCode {
776 CircularDependency,
777 InvalidNaming,
778 UnknownRequirement,
779 }
780
781 let py = to_python::<ValidationErrorCode>(false);
782 insta::assert_snapshot!(py);
783 }
784
785 #[test]
786 fn test_enum_rename_individual() {
787 #[derive(Facet)]
788 #[repr(u8)]
789 enum GitStatus {
790 #[facet(rename = "dirty")]
791 Dirty,
792 #[facet(rename = "staged")]
793 Staged,
794 #[facet(rename = "clean")]
795 Clean,
796 }
797
798 let py = to_python::<GitStatus>(false);
799 insta::assert_snapshot!(py);
800 }
801
802 #[test]
803 fn test_struct_rename_all_camel_case() {
804 #[derive(Facet)]
805 #[facet(rename_all = "camelCase")]
806 struct ApiResponse {
807 user_name: String,
808 created_at: String,
809 is_active: bool,
810 }
811
812 let py = to_python::<ApiResponse>(false);
813 insta::assert_snapshot!(py);
814 }
815
816 #[test]
817 fn test_struct_rename_individual() {
818 #[derive(Facet)]
819 struct UserProfile {
820 #[facet(rename = "userName")]
821 user_name: String,
822 #[facet(rename = "emailAddress")]
823 email: String,
824 }
825
826 let py = to_python::<UserProfile>(false);
827 insta::assert_snapshot!(py);
828 }
829
830 #[test]
831 fn test_enum_with_data_rename_all() {
832 #[derive(Facet)]
833 #[facet(rename_all = "snake_case")]
834 #[repr(C)]
835 #[allow(dead_code)]
836 enum Message {
837 TextMessage { content: String },
838 ImageUpload { url: String, width: u32 },
839 }
840
841 let py = to_python::<Message>(false);
842 insta::assert_snapshot!(py);
843 }
844
845 #[test]
846 fn test_unit_struct() {
847 #[derive(Facet)]
848 struct Empty;
849
850 let py = to_python::<Empty>(false);
851 insta::assert_snapshot!(py);
852 }
853
854 #[test]
855 fn test_tuple_struct() {
856 #[derive(Facet)]
857 struct Point(f32, f64);
858
859 let py = to_python::<Point>(false);
860 insta::assert_snapshot!(py);
861 }
862
863 #[test]
864 fn test_newtype_struct() {
865 #[derive(Facet)]
866 struct UserId(u64);
867
868 let py = to_python::<UserId>(false);
869 insta::assert_snapshot!(py);
870 }
871
872 #[test]
873 fn test_hashmap() {
874 use std::collections::HashMap;
875
876 #[derive(Facet)]
877 struct Registry {
878 entries: HashMap<String, i32>,
879 }
880
881 let py = to_python::<Registry>(false);
882 insta::assert_snapshot!(py);
883 }
884
885 #[test]
886 fn test_mixed_enum_variants() {
887 #[derive(Facet)]
888 #[repr(C)]
889 #[allow(dead_code)]
890 enum Event {
891 Empty,
893 Id(u64),
895 Data { name: String, value: f64 },
897 }
898
899 let py = to_python::<Event>(false);
900 insta::assert_snapshot!(py);
901 }
902
903 #[test]
904 fn test_with_imports() {
905 #[derive(Facet)]
906 struct User {
907 name: String,
908 age: u32,
909 }
910
911 let py = to_python::<User>(true);
912 insta::assert_snapshot!(py);
913 }
914
915 #[test]
916 fn test_enum_with_imports() {
917 #[derive(Facet)]
918 #[repr(u8)]
919 enum Status {
920 Active,
921 Inactive,
922 }
923
924 let py = to_python::<Status>(true);
925 insta::assert_snapshot!(py);
926 }
927
928 #[test]
929 fn test_transparent_wrapper() {
930 #[derive(Facet)]
931 #[facet(transparent)]
932 struct UserId(String);
933
934 let py = to_python::<UserId>(false);
935 insta::assert_snapshot!(py);
937 }
938
939 #[test]
940 fn test_transparent_wrapper_with_inner_type() {
941 #[derive(Facet)]
942 struct Inner {
943 value: i32,
944 }
945
946 #[derive(Facet)]
947 #[facet(transparent)]
948 struct Wrapper(Inner);
949
950 let py = to_python::<Wrapper>(false);
951 insta::assert_snapshot!(py);
953 }
954
955 #[test]
956 fn test_struct_with_tuple_field() {
957 #[derive(Facet)]
958 struct Container {
959 coordinates: (i32, i32),
961 }
962
963 let py = to_python::<Container>(false);
964 insta::assert_snapshot!(py);
966 }
967
968 #[test]
969 fn test_struct_with_reserved_keyword_field() {
970 #[derive(Facet)]
971 struct TradeOrder {
972 from: f64,
973 to: f64,
974 quantity: f64,
975 }
976
977 let py = to_python::<TradeOrder>(false);
978 insta::assert_snapshot!(py);
980 }
981
982 #[test]
983 fn test_struct_with_multiple_reserved_keywords() {
984 #[derive(Facet)]
985 struct ControlFlow {
986 r#if: bool,
987 r#else: String,
988 r#return: i32,
989 }
990
991 let py = to_python::<ControlFlow>(false);
992 insta::assert_snapshot!(py);
994 }
995
996 #[test]
997 fn test_enum_variant_name_is_reserved_keyword() {
998 #[derive(Facet)]
999 #[repr(C)]
1000 #[facet(rename_all = "snake_case")]
1001 #[allow(dead_code)]
1002 enum ImportSource {
1003 From(String),
1005 Url(String),
1007 }
1008
1009 let py = to_python::<ImportSource>(false);
1010 insta::assert_snapshot!(py);
1013 }
1014
1015 #[test]
1016 fn test_enum_data_variant_with_reserved_keyword_field() {
1017 #[derive(Facet)]
1018 #[repr(C)]
1019 #[allow(dead_code)]
1020 enum Transfer {
1021 Move {
1023 from: String,
1024 to: String,
1025 amount: f64,
1026 },
1027 Cancel,
1029 }
1030
1031 let py = to_python::<Transfer>(false);
1032 insta::assert_snapshot!(py);
1035 }
1036
1037 #[test]
1038 fn test_hashmap_with_integer_keys() {
1039 use std::collections::HashMap;
1040
1041 #[derive(Facet)]
1042 struct IntKeyedMap {
1043 counts: HashMap<i32, String>,
1045 }
1046
1047 let py = to_python::<IntKeyedMap>(false);
1048 insta::assert_snapshot!(py);
1049 }
1050
1051 #[test]
1052 fn test_empty_tuple_struct() {
1053 #[derive(Facet)]
1054 struct EmptyTuple();
1055
1056 let py = to_python::<EmptyTuple>(false);
1057 insta::assert_snapshot!(py);
1058 }
1059
1060 #[test]
1061 fn test_hashmap_with_enum_keys() {
1062 use std::collections::HashMap;
1063
1064 #[derive(Facet, Hash, PartialEq, Eq)]
1065 #[repr(u8)]
1066 enum Priority {
1067 Low,
1068 Medium,
1069 High,
1070 }
1071
1072 #[derive(Facet)]
1073 struct TaskMap {
1074 tasks: HashMap<Priority, String>,
1075 }
1076
1077 let py = to_python::<TaskMap>(false);
1078 insta::assert_snapshot!(py);
1079 }
1080
1081 #[test]
1082 fn test_enum_tuple_variant() {
1083 #[derive(Facet)]
1084 #[repr(C)]
1085 #[allow(dead_code)]
1086 enum TupleVariant {
1087 Point(i32, i32),
1088 }
1089 let py = to_python::<TupleVariant>(false);
1090 insta::assert_snapshot!(py);
1091 }
1092
1093 #[test]
1094 fn test_enum_struct_variant_forward_reference() {
1095 #[derive(Facet)]
1100 #[repr(C)]
1101 #[allow(dead_code)]
1102 enum Message {
1103 Data { name: String, value: f64 },
1105 }
1106 let py = to_python::<Message>(false);
1107 insta::assert_snapshot!(py);
1108 }
1109
1110 #[test]
1111 fn test_functional_typed_dict_no_type_keyword() {
1112 #[derive(Facet)]
1114 struct Bug {
1115 from: Option<String>,
1116 }
1117
1118 let py = to_python::<Bug>(false);
1119 assert!(
1120 !py.starts_with("type "),
1121 "functional TypedDict should NOT start with `type` keyword, got:\n{py}"
1122 );
1123 insta::assert_snapshot!(py);
1124 }
1125
1126 #[test]
1127 fn test_functional_typed_dict_forward_ref_quoted() {
1128 #[derive(Facet)]
1130 #[allow(dead_code)]
1131 struct Recipient {
1132 name: String,
1133 }
1134
1135 #[derive(Facet)]
1136 #[allow(dead_code)]
1137 struct Addr {
1138 from: String,
1139 to: Recipient,
1140 }
1141
1142 let py = to_python::<Addr>(false);
1143 assert!(
1144 py.contains("Required[\"Recipient\"]"),
1145 "forward reference in functional TypedDict should be quoted, got:\n{py}"
1146 );
1147 insta::assert_snapshot!(py);
1148 }
1149}