ploidy_codegen_rust/
primitive.rs

1use ploidy_core::ir::{IrPrimitiveView, PrimitiveIrType, View};
2use proc_macro2::TokenStream;
3use quote::{ToTokens, TokenStreamExt, quote};
4
5use super::config::DateTimeFormat;
6
7#[derive(Clone, Copy, Debug)]
8pub struct CodegenPrimitive<'a> {
9    ty: &'a IrPrimitiveView<'a>,
10}
11
12impl<'a> CodegenPrimitive<'a> {
13    pub fn new(ty: &'a IrPrimitiveView<'a>) -> Self {
14        Self { ty }
15    }
16}
17
18impl<'a> ToTokens for CodegenPrimitive<'a> {
19    fn to_tokens(&self, tokens: &mut TokenStream) {
20        tokens.append_all(match self.ty.ty() {
21            PrimitiveIrType::String => quote! { ::std::string::String },
22            PrimitiveIrType::I8 => quote! { i8 },
23            PrimitiveIrType::U8 => quote! { u8 },
24            PrimitiveIrType::I16 => quote! { i16 },
25            PrimitiveIrType::U16 => quote! { u16 },
26            PrimitiveIrType::I32 => quote! { i32 },
27            PrimitiveIrType::U32 => quote! { u32 },
28            PrimitiveIrType::I64 => quote! { i64 },
29            PrimitiveIrType::U64 => quote! { u64 },
30            PrimitiveIrType::F32 => quote! { f32 },
31            PrimitiveIrType::F64 => quote! { f64 },
32            PrimitiveIrType::Bool => quote! { bool },
33            PrimitiveIrType::DateTime => {
34                let format = self
35                    .ty
36                    .extensions()
37                    .get::<DateTimeFormat>()
38                    .as_deref()
39                    .copied()
40                    .unwrap_or_default();
41                match format {
42                    DateTimeFormat::Rfc3339 => {
43                        quote! { ::chrono::DateTime<::chrono::Utc> }
44                    }
45                    DateTimeFormat::UnixSeconds => {
46                        quote! { ::ploidy_util::date_time::UnixSeconds }
47                    }
48                    DateTimeFormat::UnixMilliseconds => {
49                        quote! { ::ploidy_util::date_time::UnixMilliseconds }
50                    }
51                    DateTimeFormat::UnixMicroseconds => {
52                        quote! { ::ploidy_util::date_time::UnixMicroseconds }
53                    }
54                    DateTimeFormat::UnixNanoseconds => {
55                        quote! { ::ploidy_util::date_time::UnixNanoseconds }
56                    }
57                }
58            }
59            PrimitiveIrType::UnixTime => quote! { ::ploidy_util::date_time::UnixSeconds },
60            PrimitiveIrType::Date => quote! { ::chrono::NaiveDate },
61            PrimitiveIrType::Url => quote! { ::url::Url },
62            PrimitiveIrType::Uuid => quote! { ::uuid::Uuid },
63            PrimitiveIrType::Bytes => quote! { ::ploidy_util::binary::Base64 },
64            PrimitiveIrType::Binary => quote! { ::serde_bytes::ByteBuf },
65        });
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    use itertools::Itertools;
74    use ploidy_core::{
75        ir::{IrGraph, IrSpec},
76        parse::Document,
77    };
78    use pretty_assertions::assert_eq;
79    use syn::parse_quote;
80
81    use crate::{CodegenConfig, CodegenGraph, DateTimeFormat};
82
83    #[test]
84    fn test_codegen_primitive_string() {
85        let doc = Document::from_yaml(indoc::indoc! {"
86            openapi: 3.0.0
87            info:
88              title: Test
89              version: 1.0.0
90            paths: {}
91            components:
92              schemas:
93                Test:
94                  type: string
95        "})
96        .unwrap();
97        let spec = IrSpec::from_doc(&doc).unwrap();
98        let graph = CodegenGraph::new(IrGraph::new(&spec));
99        let primitives = graph.primitives().collect_vec();
100        let [ty] = &*primitives else {
101            panic!("expected string; got `{primitives:?}`");
102        };
103        let p = CodegenPrimitive::new(ty);
104        let actual: syn::Type = parse_quote!(#p);
105        let expected: syn::Type = parse_quote!(::std::string::String);
106        assert_eq!(actual, expected);
107    }
108
109    #[test]
110    fn test_codegen_primitive_i8() {
111        let doc = Document::from_yaml(indoc::indoc! {"
112            openapi: 3.0.0
113            info:
114              title: Test
115              version: 1.0.0
116            paths: {}
117            components:
118              schemas:
119                Test:
120                  type: object
121                  required: [value]
122                  properties:
123                    value:
124                      type: integer
125                      format: int8
126        "})
127        .unwrap();
128        let spec = IrSpec::from_doc(&doc).unwrap();
129        let graph = CodegenGraph::new(IrGraph::new(&spec));
130        let primitives = graph.primitives().collect_vec();
131        let [ty] = &*primitives else {
132            panic!("expected i8; got `{primitives:?}`");
133        };
134        let p = CodegenPrimitive::new(ty);
135        let actual: syn::Type = parse_quote!(#p);
136        let expected: syn::Type = parse_quote!(i8);
137        assert_eq!(actual, expected);
138    }
139
140    #[test]
141    fn test_codegen_primitive_u8() {
142        let doc = Document::from_yaml(indoc::indoc! {"
143            openapi: 3.0.0
144            info:
145              title: Test
146              version: 1.0.0
147            paths: {}
148            components:
149              schemas:
150                Test:
151                  type: object
152                  required: [value]
153                  properties:
154                    value:
155                      type: integer
156                      format: uint8
157        "})
158        .unwrap();
159        let spec = IrSpec::from_doc(&doc).unwrap();
160        let graph = CodegenGraph::new(IrGraph::new(&spec));
161        let primitives = graph.primitives().collect_vec();
162        let [ty] = &*primitives else {
163            panic!("expected u8; got `{primitives:?}`");
164        };
165        let p = CodegenPrimitive::new(ty);
166        let actual: syn::Type = parse_quote!(#p);
167        let expected: syn::Type = parse_quote!(u8);
168        assert_eq!(actual, expected);
169    }
170
171    #[test]
172    fn test_codegen_primitive_i16() {
173        let doc = Document::from_yaml(indoc::indoc! {"
174            openapi: 3.0.0
175            info:
176              title: Test
177              version: 1.0.0
178            paths: {}
179            components:
180              schemas:
181                Test:
182                  type: object
183                  required: [value]
184                  properties:
185                    value:
186                      type: integer
187                      format: int16
188        "})
189        .unwrap();
190        let spec = IrSpec::from_doc(&doc).unwrap();
191        let graph = CodegenGraph::new(IrGraph::new(&spec));
192        let primitives = graph.primitives().collect_vec();
193        let [ty] = &*primitives else {
194            panic!("expected i16; got `{primitives:?}`");
195        };
196        let p = CodegenPrimitive::new(ty);
197        let actual: syn::Type = parse_quote!(#p);
198        let expected: syn::Type = parse_quote!(i16);
199        assert_eq!(actual, expected);
200    }
201
202    #[test]
203    fn test_codegen_primitive_u16() {
204        let doc = Document::from_yaml(indoc::indoc! {"
205            openapi: 3.0.0
206            info:
207              title: Test
208              version: 1.0.0
209            paths: {}
210            components:
211              schemas:
212                Test:
213                  type: object
214                  required: [value]
215                  properties:
216                    value:
217                      type: integer
218                      format: uint16
219        "})
220        .unwrap();
221        let spec = IrSpec::from_doc(&doc).unwrap();
222        let graph = CodegenGraph::new(IrGraph::new(&spec));
223        let primitives = graph.primitives().collect_vec();
224        let [ty] = &*primitives else {
225            panic!("expected u16; got `{primitives:?}`");
226        };
227        let p = CodegenPrimitive::new(ty);
228        let actual: syn::Type = parse_quote!(#p);
229        let expected: syn::Type = parse_quote!(u16);
230        assert_eq!(actual, expected);
231    }
232
233    #[test]
234    fn test_codegen_primitive_i32() {
235        let doc = Document::from_yaml(indoc::indoc! {"
236            openapi: 3.0.0
237            info:
238              title: Test
239              version: 1.0.0
240            paths: {}
241            components:
242              schemas:
243                Test:
244                  type: object
245                  required: [value]
246                  properties:
247                    value:
248                      type: integer
249                      format: int32
250        "})
251        .unwrap();
252        let spec = IrSpec::from_doc(&doc).unwrap();
253        let graph = CodegenGraph::new(IrGraph::new(&spec));
254        let primitives = graph.primitives().collect_vec();
255        let [ty] = &*primitives else {
256            panic!("expected string; got `{primitives:?}`");
257        };
258        let p = CodegenPrimitive::new(ty);
259        let actual: syn::Type = parse_quote!(#p);
260        let expected: syn::Type = parse_quote!(i32);
261        assert_eq!(actual, expected);
262    }
263
264    #[test]
265    fn test_codegen_primitive_u32() {
266        let doc = Document::from_yaml(indoc::indoc! {"
267            openapi: 3.0.0
268            info:
269              title: Test
270              version: 1.0.0
271            paths: {}
272            components:
273              schemas:
274                Test:
275                  type: object
276                  required: [value]
277                  properties:
278                    value:
279                      type: integer
280                      format: uint32
281        "})
282        .unwrap();
283        let spec = IrSpec::from_doc(&doc).unwrap();
284        let graph = CodegenGraph::new(IrGraph::new(&spec));
285        let primitives = graph.primitives().collect_vec();
286        let [ty] = &*primitives else {
287            panic!("expected u32; got `{primitives:?}`");
288        };
289        let p = CodegenPrimitive::new(ty);
290        let actual: syn::Type = parse_quote!(#p);
291        let expected: syn::Type = parse_quote!(u32);
292        assert_eq!(actual, expected);
293    }
294
295    #[test]
296    fn test_codegen_primitive_i64() {
297        let doc = Document::from_yaml(indoc::indoc! {"
298            openapi: 3.0.0
299            info:
300              title: Test
301              version: 1.0.0
302            paths: {}
303            components:
304              schemas:
305                Test:
306                  type: object
307                  required: [value]
308                  properties:
309                    value:
310                      type: integer
311                      format: int64
312        "})
313        .unwrap();
314        let spec = IrSpec::from_doc(&doc).unwrap();
315        let graph = CodegenGraph::new(IrGraph::new(&spec));
316        let primitives = graph.primitives().collect_vec();
317        let [ty] = &*primitives else {
318            panic!("expected i64; got `{primitives:?}`");
319        };
320        let p = CodegenPrimitive::new(ty);
321        let actual: syn::Type = parse_quote!(#p);
322        let expected: syn::Type = parse_quote!(i64);
323        assert_eq!(actual, expected);
324    }
325
326    #[test]
327    fn test_codegen_primitive_u64() {
328        let doc = Document::from_yaml(indoc::indoc! {"
329            openapi: 3.0.0
330            info:
331              title: Test
332              version: 1.0.0
333            paths: {}
334            components:
335              schemas:
336                Test:
337                  type: object
338                  required: [value]
339                  properties:
340                    value:
341                      type: integer
342                      format: uint64
343        "})
344        .unwrap();
345        let spec = IrSpec::from_doc(&doc).unwrap();
346        let graph = CodegenGraph::new(IrGraph::new(&spec));
347        let primitives = graph.primitives().collect_vec();
348        let [ty] = &*primitives else {
349            panic!("expected u64; got `{primitives:?}`");
350        };
351        let p = CodegenPrimitive::new(ty);
352        let actual: syn::Type = parse_quote!(#p);
353        let expected: syn::Type = parse_quote!(u64);
354        assert_eq!(actual, expected);
355    }
356
357    #[test]
358    fn test_codegen_primitive_f32() {
359        let doc = Document::from_yaml(indoc::indoc! {"
360            openapi: 3.0.0
361            info:
362              title: Test
363              version: 1.0.0
364            paths: {}
365            components:
366              schemas:
367                Test:
368                  type: object
369                  required: [value]
370                  properties:
371                    value:
372                      type: number
373                      format: float
374        "})
375        .unwrap();
376        let spec = IrSpec::from_doc(&doc).unwrap();
377        let graph = CodegenGraph::new(IrGraph::new(&spec));
378        let primitives = graph.primitives().collect_vec();
379        let [ty] = &*primitives else {
380            panic!("expected f32; got `{primitives:?}`");
381        };
382        let p = CodegenPrimitive::new(ty);
383        let actual: syn::Type = parse_quote!(#p);
384        let expected: syn::Type = parse_quote!(f32);
385        assert_eq!(actual, expected);
386    }
387
388    #[test]
389    fn test_codegen_primitive_f64() {
390        let doc = Document::from_yaml(indoc::indoc! {"
391            openapi: 3.0.0
392            info:
393              title: Test
394              version: 1.0.0
395            paths: {}
396            components:
397              schemas:
398                Test:
399                  type: object
400                  required: [value]
401                  properties:
402                    value:
403                      type: number
404                      format: double
405        "})
406        .unwrap();
407        let spec = IrSpec::from_doc(&doc).unwrap();
408        let graph = CodegenGraph::new(IrGraph::new(&spec));
409        let primitives = graph.primitives().collect_vec();
410        let [ty] = &*primitives else {
411            panic!("expected f64; got `{primitives:?}`");
412        };
413        let p = CodegenPrimitive::new(ty);
414        let actual: syn::Type = parse_quote!(#p);
415        let expected: syn::Type = parse_quote!(f64);
416        assert_eq!(actual, expected);
417    }
418
419    #[test]
420    fn test_codegen_primitive_bool() {
421        let doc = Document::from_yaml(indoc::indoc! {"
422            openapi: 3.0.0
423            info:
424              title: Test
425              version: 1.0.0
426            paths: {}
427            components:
428              schemas:
429                Test:
430                  type: object
431                  required: [value]
432                  properties:
433                    value:
434                      type: boolean
435        "})
436        .unwrap();
437        let spec = IrSpec::from_doc(&doc).unwrap();
438        let graph = CodegenGraph::new(IrGraph::new(&spec));
439        let primitives = graph.primitives().collect_vec();
440        let [ty] = &*primitives else {
441            panic!("expected bool; got `{primitives:?}`");
442        };
443        let p = CodegenPrimitive::new(ty);
444        let actual: syn::Type = parse_quote!(#p);
445        let expected: syn::Type = parse_quote!(bool);
446        assert_eq!(actual, expected);
447    }
448
449    #[test]
450    fn test_codegen_primitive_datetime_default_rfc3339() {
451        let doc = Document::from_yaml(indoc::indoc! {"
452            openapi: 3.0.0
453            info:
454              title: Test
455              version: 1.0.0
456            paths: {}
457            components:
458              schemas:
459                Test:
460                  type: object
461                  required: [value]
462                  properties:
463                    value:
464                      type: string
465                      format: date-time
466        "})
467        .unwrap();
468        let spec = IrSpec::from_doc(&doc).unwrap();
469        // Default config uses RFC 3339.
470        let graph = CodegenGraph::new(IrGraph::new(&spec));
471        let primitives = graph.primitives().collect_vec();
472        let [ty] = &*primitives else {
473            panic!("expected datetime; got `{primitives:?}`");
474        };
475        let p = CodegenPrimitive::new(ty);
476        let actual: syn::Type = parse_quote!(#p);
477        let expected: syn::Type = parse_quote!(::chrono::DateTime<::chrono::Utc>);
478        assert_eq!(actual, expected);
479    }
480
481    #[test]
482    fn test_codegen_primitive_datetime_unix_milliseconds() {
483        let doc = Document::from_yaml(indoc::indoc! {"
484            openapi: 3.0.0
485            info:
486              title: Test
487              version: 1.0.0
488            paths: {}
489            components:
490              schemas:
491                Test:
492                  type: object
493                  required: [value]
494                  properties:
495                    value:
496                      type: string
497                      format: date-time
498        "})
499        .unwrap();
500        let spec = IrSpec::from_doc(&doc).unwrap();
501        let graph = CodegenGraph::with_config(
502            IrGraph::new(&spec),
503            &CodegenConfig {
504                date_time_format: DateTimeFormat::UnixMilliseconds,
505            },
506        );
507        let primitives = graph.primitives().collect_vec();
508        let [ty] = &*primitives else {
509            panic!("expected datetime; got `{primitives:?}`");
510        };
511        let p = CodegenPrimitive::new(ty);
512        let actual: syn::Type = parse_quote!(#p);
513        let expected: syn::Type = parse_quote!(::ploidy_util::date_time::UnixMilliseconds);
514        assert_eq!(actual, expected);
515    }
516
517    #[test]
518    fn test_codegen_primitive_datetime_unix_seconds() {
519        let doc = Document::from_yaml(indoc::indoc! {"
520            openapi: 3.0.0
521            info:
522              title: Test
523              version: 1.0.0
524            paths: {}
525            components:
526              schemas:
527                Test:
528                  type: object
529                  required: [value]
530                  properties:
531                    value:
532                      type: string
533                      format: date-time
534        "})
535        .unwrap();
536        let spec = IrSpec::from_doc(&doc).unwrap();
537        let graph = CodegenGraph::with_config(
538            IrGraph::new(&spec),
539            &CodegenConfig {
540                date_time_format: DateTimeFormat::UnixSeconds,
541            },
542        );
543        let primitives = graph.primitives().collect_vec();
544        let [ty] = &*primitives else {
545            panic!("expected datetime; got `{primitives:?}`");
546        };
547        let p = CodegenPrimitive::new(ty);
548        let actual: syn::Type = parse_quote!(#p);
549        let expected: syn::Type = parse_quote!(::ploidy_util::date_time::UnixSeconds);
550        assert_eq!(actual, expected);
551    }
552
553    #[test]
554    fn test_codegen_primitive_datetime_unix_microseconds() {
555        let doc = Document::from_yaml(indoc::indoc! {"
556            openapi: 3.0.0
557            info:
558              title: Test
559              version: 1.0.0
560            paths: {}
561            components:
562              schemas:
563                Test:
564                  type: object
565                  required: [value]
566                  properties:
567                    value:
568                      type: string
569                      format: date-time
570        "})
571        .unwrap();
572        let spec = IrSpec::from_doc(&doc).unwrap();
573        let graph = CodegenGraph::with_config(
574            IrGraph::new(&spec),
575            &CodegenConfig {
576                date_time_format: DateTimeFormat::UnixMicroseconds,
577            },
578        );
579        let primitives = graph.primitives().collect_vec();
580        let [ty] = &*primitives else {
581            panic!("expected datetime; got `{primitives:?}`");
582        };
583        let p = CodegenPrimitive::new(ty);
584        let actual: syn::Type = parse_quote!(#p);
585        let expected: syn::Type = parse_quote!(::ploidy_util::date_time::UnixMicroseconds);
586        assert_eq!(actual, expected);
587    }
588
589    #[test]
590    fn test_codegen_primitive_datetime_unix_nanoseconds() {
591        let doc = Document::from_yaml(indoc::indoc! {"
592            openapi: 3.0.0
593            info:
594              title: Test
595              version: 1.0.0
596            paths: {}
597            components:
598              schemas:
599                Test:
600                  type: object
601                  required: [value]
602                  properties:
603                    value:
604                      type: string
605                      format: date-time
606        "})
607        .unwrap();
608        let spec = IrSpec::from_doc(&doc).unwrap();
609        let graph = CodegenGraph::with_config(
610            IrGraph::new(&spec),
611            &CodegenConfig {
612                date_time_format: DateTimeFormat::UnixNanoseconds,
613            },
614        );
615        let primitives = graph.primitives().collect_vec();
616        let [ty] = &*primitives else {
617            panic!("expected datetime; got `{primitives:?}`");
618        };
619        let p = CodegenPrimitive::new(ty);
620        let actual: syn::Type = parse_quote!(#p);
621        let expected: syn::Type = parse_quote!(::ploidy_util::date_time::UnixNanoseconds);
622        assert_eq!(actual, expected);
623    }
624
625    #[test]
626    fn test_codegen_primitive_date() {
627        let doc = Document::from_yaml(indoc::indoc! {"
628            openapi: 3.0.0
629            info:
630              title: Test
631              version: 1.0.0
632            paths: {}
633            components:
634              schemas:
635                Test:
636                  type: object
637                  required: [value]
638                  properties:
639                    value:
640                      type: string
641                      format: date
642        "})
643        .unwrap();
644        let spec = IrSpec::from_doc(&doc).unwrap();
645        let graph = CodegenGraph::new(IrGraph::new(&spec));
646        let primitives = graph.primitives().collect_vec();
647        let [ty] = &*primitives else {
648            panic!("expected date; got `{primitives:?}`");
649        };
650        let p = CodegenPrimitive::new(ty);
651        let actual: syn::Type = parse_quote!(#p);
652        let expected: syn::Type = parse_quote!(::chrono::NaiveDate);
653        assert_eq!(actual, expected);
654    }
655
656    #[test]
657    fn test_codegen_primitive_url() {
658        let doc = Document::from_yaml(indoc::indoc! {"
659            openapi: 3.0.0
660            info:
661              title: Test
662              version: 1.0.0
663            paths: {}
664            components:
665              schemas:
666                Test:
667                  type: object
668                  required: [value]
669                  properties:
670                    value:
671                      type: string
672                      format: uri
673        "})
674        .unwrap();
675        let spec = IrSpec::from_doc(&doc).unwrap();
676        let graph = CodegenGraph::new(IrGraph::new(&spec));
677        let primitives = graph.primitives().collect_vec();
678        let [ty] = &*primitives else {
679            panic!("expected url; got `{primitives:?}`");
680        };
681        let p = CodegenPrimitive::new(ty);
682        let actual: syn::Type = parse_quote!(#p);
683        let expected: syn::Type = parse_quote!(::url::Url);
684        assert_eq!(actual, expected);
685    }
686
687    #[test]
688    fn test_codegen_primitive_uuid() {
689        let doc = Document::from_yaml(indoc::indoc! {"
690            openapi: 3.0.0
691            info:
692              title: Test
693              version: 1.0.0
694            paths: {}
695            components:
696              schemas:
697                Test:
698                  type: object
699                  required: [value]
700                  properties:
701                    value:
702                      type: string
703                      format: uuid
704        "})
705        .unwrap();
706        let spec = IrSpec::from_doc(&doc).unwrap();
707        let graph = CodegenGraph::new(IrGraph::new(&spec));
708        let primitives = graph.primitives().collect_vec();
709        let [ty] = &*primitives else {
710            panic!("expected uuid; got `{primitives:?}`");
711        };
712        let p = CodegenPrimitive::new(ty);
713        let actual: syn::Type = parse_quote!(#p);
714        let expected: syn::Type = parse_quote!(::uuid::Uuid);
715        assert_eq!(actual, expected);
716    }
717
718    #[test]
719    fn test_codegen_primitive_bytes() {
720        let doc = Document::from_yaml(indoc::indoc! {"
721            openapi: 3.0.0
722            info:
723              title: Test
724              version: 1.0.0
725            paths: {}
726            components:
727              schemas:
728                Test:
729                  type: object
730                  required: [value]
731                  properties:
732                    value:
733                      type: string
734                      format: byte
735        "})
736        .unwrap();
737        let spec = IrSpec::from_doc(&doc).unwrap();
738        let graph = CodegenGraph::new(IrGraph::new(&spec));
739        let primitives = graph.primitives().collect_vec();
740        let [ty] = &*primitives else {
741            panic!("expected bytes; got `{primitives:?}`");
742        };
743        let p = CodegenPrimitive::new(ty);
744        let actual: syn::Type = parse_quote!(#p);
745        let expected: syn::Type = parse_quote!(::ploidy_util::binary::Base64);
746        assert_eq!(actual, expected);
747    }
748
749    #[test]
750    fn test_codegen_primitive_binary() {
751        let doc = Document::from_yaml(indoc::indoc! {"
752            openapi: 3.0.0
753            info:
754              title: Test
755              version: 1.0.0
756            paths: {}
757            components:
758              schemas:
759                Test:
760                  type: object
761                  required: [value]
762                  properties:
763                    value:
764                      type: string
765                      format: binary
766        "})
767        .unwrap();
768        let spec = IrSpec::from_doc(&doc).unwrap();
769        let graph = CodegenGraph::new(IrGraph::new(&spec));
770        let primitives = graph.primitives().collect_vec();
771        let [ty] = &*primitives else {
772            panic!("expected binary; got `{primitives:?}`");
773        };
774        let p = CodegenPrimitive::new(ty);
775        let actual: syn::Type = parse_quote!(#p);
776        let expected: syn::Type = parse_quote!(::serde_bytes::ByteBuf);
777        assert_eq!(actual, expected);
778    }
779}