ploidy_codegen_rust/
resource.rs

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