ploidy_codegen_rust/
resource.rs

1use heck::ToSnakeCase;
2use itertools::Itertools;
3use ploidy_core::{
4    codegen::IntoCode,
5    ir::{InlineIrTypePathRoot, InlineIrTypeView, IrOperationView},
6};
7use proc_macro2::TokenStream;
8use quote::{ToTokens, TokenStreamExt, quote};
9
10use super::{
11    enum_::CodegenEnum, naming::CodegenTypeName, operation::CodegenOperation,
12    struct_::CodegenStruct, tagged::CodegenTagged, untagged::CodegenUntagged,
13};
14
15/// Generates a feature-gated `impl Client` block for a resource,
16/// with all its operations.
17pub struct CodegenResource<'a> {
18    resource: &'a str,
19    operations: &'a [IrOperationView<'a>],
20}
21
22impl<'a> CodegenResource<'a> {
23    pub fn new(resource: &'a str, operations: &'a [IrOperationView<'a>]) -> Self {
24        Self {
25            resource,
26            operations,
27        }
28    }
29}
30
31impl ToTokens for CodegenResource<'_> {
32    fn to_tokens(&self, tokens: &mut TokenStream) {
33        let feature_name = self.resource;
34        let methods: Vec<TokenStream> = self
35            .operations
36            .iter()
37            .map(|view| CodegenOperation::new(view).into_token_stream())
38            .collect();
39
40        let mut inlines = self
41            .operations
42            .iter()
43            .flat_map(|op| op.inlines())
44            .filter(|ty| {
45                // Only emit Rust definitions for inline types contained
46                // within the operation. Inline types contained within schemas
47                // that the operation _references_ will be generated as part of
48                // `CodegenSchemaType`.
49                matches!(ty.path().root, InlineIrTypePathRoot::Resource(r) if r == self.resource)
50            })
51            .collect_vec();
52        inlines.sort_by(|a, b| {
53            CodegenTypeName::Inline(a)
54                .into_sort_key()
55                .cmp(&CodegenTypeName::Inline(b).into_sort_key())
56        });
57        let mut inlines = inlines.into_iter().map(|view| {
58            let name = CodegenTypeName::Inline(&view);
59            match &view {
60                InlineIrTypeView::Enum(_, view) => CodegenEnum::new(name, view).into_token_stream(),
61                InlineIrTypeView::Struct(_, view) => {
62                    CodegenStruct::new(name, view).into_token_stream()
63                }
64                InlineIrTypeView::Tagged(_, view) => {
65                    CodegenTagged::new(name, view).into_token_stream()
66                }
67                InlineIrTypeView::Untagged(_, view) => {
68                    CodegenUntagged::new(name, view).into_token_stream()
69                }
70            }
71        });
72        let fields_module = inlines.next().map(|head| {
73            quote! {
74                pub mod types {
75                    #head
76                    #(#inlines)*
77                }
78            }
79        });
80
81        tokens.append_all(quote! {
82            #[cfg(feature = #feature_name)]
83            impl crate::client::Client {
84                #(#methods)*
85            }
86            #fields_module
87        });
88    }
89}
90
91impl IntoCode for CodegenResource<'_> {
92    type Code = (String, TokenStream);
93
94    fn into_code(self) -> Self::Code {
95        (
96            format!("src/client/{}.rs", self.resource.to_snake_case()),
97            self.into_token_stream(),
98        )
99    }
100}