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::I32 => quote! { i32 },
23            PrimitiveIrType::I64 => quote! { i64 },
24            PrimitiveIrType::F32 => quote! { f32 },
25            PrimitiveIrType::F64 => quote! { f64 },
26            PrimitiveIrType::Bool => quote! { bool },
27            PrimitiveIrType::DateTime => {
28                let format = self
29                    .ty
30                    .extensions()
31                    .get::<DateTimeFormat>()
32                    .as_deref()
33                    .copied()
34                    .unwrap_or_default();
35                match format {
36                    DateTimeFormat::Rfc3339 => {
37                        quote! { ::chrono::DateTime<::chrono::Utc> }
38                    }
39                    DateTimeFormat::UnixSeconds => {
40                        quote! { ::ploidy_util::date_time::UnixSeconds }
41                    }
42                    DateTimeFormat::UnixMilliseconds => {
43                        quote! { ::ploidy_util::date_time::UnixMilliseconds }
44                    }
45                    DateTimeFormat::UnixMicroseconds => {
46                        quote! { ::ploidy_util::date_time::UnixMicroseconds }
47                    }
48                    DateTimeFormat::UnixNanoseconds => {
49                        quote! { ::ploidy_util::date_time::UnixNanoseconds }
50                    }
51                }
52            }
53            PrimitiveIrType::UnixTime => quote! { ::ploidy_util::date_time::UnixSeconds },
54            PrimitiveIrType::Date => quote! { ::chrono::NaiveDate },
55            PrimitiveIrType::Url => quote! { ::url::Url },
56            PrimitiveIrType::Uuid => quote! { ::uuid::Uuid },
57            PrimitiveIrType::Bytes => quote! { ::bytes::Bytes },
58        });
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    use itertools::Itertools;
67    use ploidy_core::{
68        ir::{IrGraph, IrSpec},
69        parse::Document,
70    };
71    use pretty_assertions::assert_eq;
72    use syn::parse_quote;
73
74    use crate::{CodegenConfig, CodegenGraph, DateTimeFormat};
75
76    #[test]
77    fn test_codegen_primitive_string() {
78        let doc = Document::from_yaml(indoc::indoc! {"
79            openapi: 3.0.0
80            info:
81              title: Test
82              version: 1.0.0
83            paths: {}
84            components:
85              schemas:
86                Test:
87                  type: string
88        "})
89        .unwrap();
90        let spec = IrSpec::from_doc(&doc).unwrap();
91        let graph = CodegenGraph::new(IrGraph::new(&spec));
92        let primitives = graph.primitives().collect_vec();
93        let [ty] = &*primitives else {
94            panic!("expected string; got `{primitives:?}`");
95        };
96        let p = CodegenPrimitive::new(ty);
97        let actual: syn::Type = parse_quote!(#p);
98        let expected: syn::Type = parse_quote!(::std::string::String);
99        assert_eq!(actual, expected);
100    }
101
102    #[test]
103    fn test_codegen_primitive_i32() {
104        let doc = Document::from_yaml(indoc::indoc! {"
105            openapi: 3.0.0
106            info:
107              title: Test
108              version: 1.0.0
109            paths: {}
110            components:
111              schemas:
112                Test:
113                  type: object
114                  required: [value]
115                  properties:
116                    value:
117                      type: integer
118                      format: int32
119        "})
120        .unwrap();
121        let spec = IrSpec::from_doc(&doc).unwrap();
122        let graph = CodegenGraph::new(IrGraph::new(&spec));
123        let primitives = graph.primitives().collect_vec();
124        let [ty] = &*primitives else {
125            panic!("expected string; got `{primitives:?}`");
126        };
127        let p = CodegenPrimitive::new(ty);
128        let actual: syn::Type = parse_quote!(#p);
129        let expected: syn::Type = parse_quote!(i32);
130        assert_eq!(actual, expected);
131    }
132
133    #[test]
134    fn test_codegen_primitive_i64() {
135        let doc = Document::from_yaml(indoc::indoc! {"
136            openapi: 3.0.0
137            info:
138              title: Test
139              version: 1.0.0
140            paths: {}
141            components:
142              schemas:
143                Test:
144                  type: object
145                  required: [value]
146                  properties:
147                    value:
148                      type: integer
149                      format: int64
150        "})
151        .unwrap();
152        let spec = IrSpec::from_doc(&doc).unwrap();
153        let graph = CodegenGraph::new(IrGraph::new(&spec));
154        let primitives = graph.primitives().collect_vec();
155        let [ty] = &*primitives else {
156            panic!("expected i64; got `{primitives:?}`");
157        };
158        let p = CodegenPrimitive::new(ty);
159        let actual: syn::Type = parse_quote!(#p);
160        let expected: syn::Type = parse_quote!(i64);
161        assert_eq!(actual, expected);
162    }
163
164    #[test]
165    fn test_codegen_primitive_f32() {
166        let doc = Document::from_yaml(indoc::indoc! {"
167            openapi: 3.0.0
168            info:
169              title: Test
170              version: 1.0.0
171            paths: {}
172            components:
173              schemas:
174                Test:
175                  type: object
176                  required: [value]
177                  properties:
178                    value:
179                      type: number
180                      format: float
181        "})
182        .unwrap();
183        let spec = IrSpec::from_doc(&doc).unwrap();
184        let graph = CodegenGraph::new(IrGraph::new(&spec));
185        let primitives = graph.primitives().collect_vec();
186        let [ty] = &*primitives else {
187            panic!("expected f32; got `{primitives:?}`");
188        };
189        let p = CodegenPrimitive::new(ty);
190        let actual: syn::Type = parse_quote!(#p);
191        let expected: syn::Type = parse_quote!(f32);
192        assert_eq!(actual, expected);
193    }
194
195    #[test]
196    fn test_codegen_primitive_f64() {
197        let doc = Document::from_yaml(indoc::indoc! {"
198            openapi: 3.0.0
199            info:
200              title: Test
201              version: 1.0.0
202            paths: {}
203            components:
204              schemas:
205                Test:
206                  type: object
207                  required: [value]
208                  properties:
209                    value:
210                      type: number
211                      format: double
212        "})
213        .unwrap();
214        let spec = IrSpec::from_doc(&doc).unwrap();
215        let graph = CodegenGraph::new(IrGraph::new(&spec));
216        let primitives = graph.primitives().collect_vec();
217        let [ty] = &*primitives else {
218            panic!("expected f64; got `{primitives:?}`");
219        };
220        let p = CodegenPrimitive::new(ty);
221        let actual: syn::Type = parse_quote!(#p);
222        let expected: syn::Type = parse_quote!(f64);
223        assert_eq!(actual, expected);
224    }
225
226    #[test]
227    fn test_codegen_primitive_bool() {
228        let doc = Document::from_yaml(indoc::indoc! {"
229            openapi: 3.0.0
230            info:
231              title: Test
232              version: 1.0.0
233            paths: {}
234            components:
235              schemas:
236                Test:
237                  type: object
238                  required: [value]
239                  properties:
240                    value:
241                      type: boolean
242        "})
243        .unwrap();
244        let spec = IrSpec::from_doc(&doc).unwrap();
245        let graph = CodegenGraph::new(IrGraph::new(&spec));
246        let primitives = graph.primitives().collect_vec();
247        let [ty] = &*primitives else {
248            panic!("expected bool; got `{primitives:?}`");
249        };
250        let p = CodegenPrimitive::new(ty);
251        let actual: syn::Type = parse_quote!(#p);
252        let expected: syn::Type = parse_quote!(bool);
253        assert_eq!(actual, expected);
254    }
255
256    #[test]
257    fn test_codegen_primitive_datetime_default_rfc3339() {
258        let doc = Document::from_yaml(indoc::indoc! {"
259            openapi: 3.0.0
260            info:
261              title: Test
262              version: 1.0.0
263            paths: {}
264            components:
265              schemas:
266                Test:
267                  type: object
268                  required: [value]
269                  properties:
270                    value:
271                      type: string
272                      format: date-time
273        "})
274        .unwrap();
275        let spec = IrSpec::from_doc(&doc).unwrap();
276        // Default config uses RFC 3339.
277        let graph = CodegenGraph::new(IrGraph::new(&spec));
278        let primitives = graph.primitives().collect_vec();
279        let [ty] = &*primitives else {
280            panic!("expected datetime; got `{primitives:?}`");
281        };
282        let p = CodegenPrimitive::new(ty);
283        let actual: syn::Type = parse_quote!(#p);
284        let expected: syn::Type = parse_quote!(::chrono::DateTime<::chrono::Utc>);
285        assert_eq!(actual, expected);
286    }
287
288    #[test]
289    fn test_codegen_primitive_datetime_unix_milliseconds() {
290        let doc = Document::from_yaml(indoc::indoc! {"
291            openapi: 3.0.0
292            info:
293              title: Test
294              version: 1.0.0
295            paths: {}
296            components:
297              schemas:
298                Test:
299                  type: object
300                  required: [value]
301                  properties:
302                    value:
303                      type: string
304                      format: date-time
305        "})
306        .unwrap();
307        let spec = IrSpec::from_doc(&doc).unwrap();
308        let graph = CodegenGraph::with_config(
309            IrGraph::new(&spec),
310            &CodegenConfig {
311                date_time_format: DateTimeFormat::UnixMilliseconds,
312            },
313        );
314        let primitives = graph.primitives().collect_vec();
315        let [ty] = &*primitives else {
316            panic!("expected datetime; got `{primitives:?}`");
317        };
318        let p = CodegenPrimitive::new(ty);
319        let actual: syn::Type = parse_quote!(#p);
320        let expected: syn::Type = parse_quote!(::ploidy_util::date_time::UnixMilliseconds);
321        assert_eq!(actual, expected);
322    }
323
324    #[test]
325    fn test_codegen_primitive_datetime_unix_seconds() {
326        let doc = Document::from_yaml(indoc::indoc! {"
327            openapi: 3.0.0
328            info:
329              title: Test
330              version: 1.0.0
331            paths: {}
332            components:
333              schemas:
334                Test:
335                  type: object
336                  required: [value]
337                  properties:
338                    value:
339                      type: string
340                      format: date-time
341        "})
342        .unwrap();
343        let spec = IrSpec::from_doc(&doc).unwrap();
344        let graph = CodegenGraph::with_config(
345            IrGraph::new(&spec),
346            &CodegenConfig {
347                date_time_format: DateTimeFormat::UnixSeconds,
348            },
349        );
350        let primitives = graph.primitives().collect_vec();
351        let [ty] = &*primitives else {
352            panic!("expected datetime; got `{primitives:?}`");
353        };
354        let p = CodegenPrimitive::new(ty);
355        let actual: syn::Type = parse_quote!(#p);
356        let expected: syn::Type = parse_quote!(::ploidy_util::date_time::UnixSeconds);
357        assert_eq!(actual, expected);
358    }
359
360    #[test]
361    fn test_codegen_primitive_datetime_unix_microseconds() {
362        let doc = Document::from_yaml(indoc::indoc! {"
363            openapi: 3.0.0
364            info:
365              title: Test
366              version: 1.0.0
367            paths: {}
368            components:
369              schemas:
370                Test:
371                  type: object
372                  required: [value]
373                  properties:
374                    value:
375                      type: string
376                      format: date-time
377        "})
378        .unwrap();
379        let spec = IrSpec::from_doc(&doc).unwrap();
380        let graph = CodegenGraph::with_config(
381            IrGraph::new(&spec),
382            &CodegenConfig {
383                date_time_format: DateTimeFormat::UnixMicroseconds,
384            },
385        );
386        let primitives = graph.primitives().collect_vec();
387        let [ty] = &*primitives else {
388            panic!("expected datetime; got `{primitives:?}`");
389        };
390        let p = CodegenPrimitive::new(ty);
391        let actual: syn::Type = parse_quote!(#p);
392        let expected: syn::Type = parse_quote!(::ploidy_util::date_time::UnixMicroseconds);
393        assert_eq!(actual, expected);
394    }
395
396    #[test]
397    fn test_codegen_primitive_datetime_unix_nanoseconds() {
398        let doc = Document::from_yaml(indoc::indoc! {"
399            openapi: 3.0.0
400            info:
401              title: Test
402              version: 1.0.0
403            paths: {}
404            components:
405              schemas:
406                Test:
407                  type: object
408                  required: [value]
409                  properties:
410                    value:
411                      type: string
412                      format: date-time
413        "})
414        .unwrap();
415        let spec = IrSpec::from_doc(&doc).unwrap();
416        let graph = CodegenGraph::with_config(
417            IrGraph::new(&spec),
418            &CodegenConfig {
419                date_time_format: DateTimeFormat::UnixNanoseconds,
420            },
421        );
422        let primitives = graph.primitives().collect_vec();
423        let [ty] = &*primitives else {
424            panic!("expected datetime; got `{primitives:?}`");
425        };
426        let p = CodegenPrimitive::new(ty);
427        let actual: syn::Type = parse_quote!(#p);
428        let expected: syn::Type = parse_quote!(::ploidy_util::date_time::UnixNanoseconds);
429        assert_eq!(actual, expected);
430    }
431
432    #[test]
433    fn test_codegen_primitive_date() {
434        let doc = Document::from_yaml(indoc::indoc! {"
435            openapi: 3.0.0
436            info:
437              title: Test
438              version: 1.0.0
439            paths: {}
440            components:
441              schemas:
442                Test:
443                  type: object
444                  required: [value]
445                  properties:
446                    value:
447                      type: string
448                      format: date
449        "})
450        .unwrap();
451        let spec = IrSpec::from_doc(&doc).unwrap();
452        let graph = CodegenGraph::new(IrGraph::new(&spec));
453        let primitives = graph.primitives().collect_vec();
454        let [ty] = &*primitives else {
455            panic!("expected date; got `{primitives:?}`");
456        };
457        let p = CodegenPrimitive::new(ty);
458        let actual: syn::Type = parse_quote!(#p);
459        let expected: syn::Type = parse_quote!(::chrono::NaiveDate);
460        assert_eq!(actual, expected);
461    }
462
463    #[test]
464    fn test_codegen_primitive_url() {
465        let doc = Document::from_yaml(indoc::indoc! {"
466            openapi: 3.0.0
467            info:
468              title: Test
469              version: 1.0.0
470            paths: {}
471            components:
472              schemas:
473                Test:
474                  type: object
475                  required: [value]
476                  properties:
477                    value:
478                      type: string
479                      format: uri
480        "})
481        .unwrap();
482        let spec = IrSpec::from_doc(&doc).unwrap();
483        let graph = CodegenGraph::new(IrGraph::new(&spec));
484        let primitives = graph.primitives().collect_vec();
485        let [ty] = &*primitives else {
486            panic!("expected url; got `{primitives:?}`");
487        };
488        let p = CodegenPrimitive::new(ty);
489        let actual: syn::Type = parse_quote!(#p);
490        let expected: syn::Type = parse_quote!(::url::Url);
491        assert_eq!(actual, expected);
492    }
493
494    #[test]
495    fn test_codegen_primitive_uuid() {
496        let doc = Document::from_yaml(indoc::indoc! {"
497            openapi: 3.0.0
498            info:
499              title: Test
500              version: 1.0.0
501            paths: {}
502            components:
503              schemas:
504                Test:
505                  type: object
506                  required: [value]
507                  properties:
508                    value:
509                      type: string
510                      format: uuid
511        "})
512        .unwrap();
513        let spec = IrSpec::from_doc(&doc).unwrap();
514        let graph = CodegenGraph::new(IrGraph::new(&spec));
515        let primitives = graph.primitives().collect_vec();
516        let [ty] = &*primitives else {
517            panic!("expected uuid; got `{primitives:?}`");
518        };
519        let p = CodegenPrimitive::new(ty);
520        let actual: syn::Type = parse_quote!(#p);
521        let expected: syn::Type = parse_quote!(::uuid::Uuid);
522        assert_eq!(actual, expected);
523    }
524
525    #[test]
526    fn test_codegen_primitive_bytes() {
527        let doc = Document::from_yaml(indoc::indoc! {"
528            openapi: 3.0.0
529            info:
530              title: Test
531              version: 1.0.0
532            paths: {}
533            components:
534              schemas:
535                Test:
536                  type: object
537                  required: [value]
538                  properties:
539                    value:
540                      type: string
541                      format: byte
542        "})
543        .unwrap();
544        let spec = IrSpec::from_doc(&doc).unwrap();
545        let graph = CodegenGraph::new(IrGraph::new(&spec));
546        let primitives = graph.primitives().collect_vec();
547        let [ty] = &*primitives else {
548            panic!("expected bytes; got `{primitives:?}`");
549        };
550        let p = CodegenPrimitive::new(ty);
551        let actual: syn::Type = parse_quote!(#p);
552        let expected: syn::Type = parse_quote!(::bytes::Bytes);
553        assert_eq!(actual, expected);
554    }
555}