Skip to main content

ploidy_codegen_rust/
schema.rs

1use ploidy_core::{codegen::IntoCode, ir::SchemaIrTypeView};
2use proc_macro2::TokenStream;
3use quote::{ToTokens, TokenStreamExt, quote};
4
5use super::{
6    enum_::CodegenEnum, inlines::CodegenInlines, naming::CodegenTypeName, struct_::CodegenStruct,
7    tagged::CodegenTagged, untagged::CodegenUntagged,
8};
9
10/// Generates a module for a named schema type.
11#[derive(Debug)]
12pub struct CodegenSchemaType<'a> {
13    ty: &'a SchemaIrTypeView<'a>,
14}
15
16impl<'a> CodegenSchemaType<'a> {
17    pub fn new(ty: &'a SchemaIrTypeView<'a>) -> Self {
18        Self { ty }
19    }
20}
21
22impl ToTokens for CodegenSchemaType<'_> {
23    fn to_tokens(&self, tokens: &mut TokenStream) {
24        let name = CodegenTypeName::Schema(self.ty);
25        let ty = match self.ty {
26            SchemaIrTypeView::Struct(_, view) => CodegenStruct::new(name, view).into_token_stream(),
27            SchemaIrTypeView::Enum(_, view) => CodegenEnum::new(name, view).into_token_stream(),
28            SchemaIrTypeView::Tagged(_, view) => CodegenTagged::new(name, view).into_token_stream(),
29            SchemaIrTypeView::Untagged(_, view) => {
30                CodegenUntagged::new(name, view).into_token_stream()
31            }
32        };
33        let inlines = CodegenInlines::Schema(self.ty);
34        tokens.append_all(quote! {
35            #ty
36            #inlines
37        });
38    }
39}
40
41impl IntoCode for CodegenSchemaType<'_> {
42    type Code = (String, TokenStream);
43
44    fn into_code(self) -> Self::Code {
45        let name = CodegenTypeName::Schema(self.ty);
46        (
47            format!("src/types/{}.rs", name.into_module_name().display()),
48            self.into_token_stream(),
49        )
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    use ploidy_core::{
58        ir::{IrGraph, IrSpec, SchemaIrTypeView},
59        parse::Document,
60    };
61    use pretty_assertions::assert_eq;
62    use syn::parse_quote;
63
64    use crate::CodegenGraph;
65
66    #[test]
67    fn test_schema_inline_types_order() {
68        // Inline types are defined in reverse alphabetical order (Zebra, Mango, Apple),
69        // to verify that they're sorted in the output.
70        let doc = Document::from_yaml(indoc::indoc! {"
71            openapi: 3.0.0
72            info:
73              title: Test API
74              version: 1.0.0
75            paths: {}
76            components:
77              schemas:
78                Container:
79                  type: object
80                  properties:
81                    zebra:
82                      type: object
83                      properties:
84                        name:
85                          type: string
86                    mango:
87                      type: object
88                      properties:
89                        name:
90                          type: string
91                    apple:
92                      type: object
93                      properties:
94                        name:
95                          type: string
96        "})
97        .unwrap();
98
99        let spec = IrSpec::from_doc(&doc).unwrap();
100        let ir = IrGraph::new(&spec);
101        let graph = CodegenGraph::new(ir);
102
103        let schema = graph.schemas().find(|s| s.name() == "Container");
104        let Some(schema @ SchemaIrTypeView::Struct(_, _)) = &schema else {
105            panic!("expected struct `Container`; got `{schema:?}`");
106        };
107
108        let codegen = CodegenSchemaType::new(schema);
109
110        let actual: syn::File = parse_quote!(#codegen);
111        // The struct fields remain in their original order (`zebra`, `mango`, `apple`),
112        // but the inline types in `mod types` should be sorted alphabetically
113        // (`Apple`, `Mango`, `Zebra`).
114        let expected: syn::File = parse_quote! {
115            #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
116            #[serde(crate = "::ploidy_util::serde")]
117            pub struct Container {
118                #[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent",)]
119                pub zebra: ::ploidy_util::absent::AbsentOr<crate::types::container::types::Zebra>,
120                #[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent",)]
121                pub mango: ::ploidy_util::absent::AbsentOr<crate::types::container::types::Mango>,
122                #[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent",)]
123                pub apple: ::ploidy_util::absent::AbsentOr<crate::types::container::types::Apple>,
124            }
125            pub mod types {
126                #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
127                #[serde(crate = "::ploidy_util::serde")]
128                pub struct Apple {
129                    #[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent",)]
130                    pub name: ::ploidy_util::absent::AbsentOr<::std::string::String>,
131                }
132                #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
133                #[serde(crate = "::ploidy_util::serde")]
134                pub struct Mango {
135                    #[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent",)]
136                    pub name: ::ploidy_util::absent::AbsentOr<::std::string::String>,
137                }
138                #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, ::ploidy_util::serde::Serialize, ::ploidy_util::serde::Deserialize)]
139                #[serde(crate = "::ploidy_util::serde")]
140                pub struct Zebra {
141                    #[serde(default, skip_serializing_if = "::ploidy_util::absent::AbsentOr::is_absent",)]
142                    pub name: ::ploidy_util::absent::AbsentOr<::std::string::String>,
143                }
144            }
145        };
146        assert_eq!(actual, expected);
147    }
148}