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, 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| match view {
51                InlineIrTypeView::Enum(path, view) => {
52                    CodegenEnum::new(CodegenTypeName::Inline(path), &view).into_token_stream()
53                }
54                InlineIrTypeView::Struct(path, view) => {
55                    CodegenStruct::new(CodegenTypeName::Inline(path), &view).into_token_stream()
56                }
57                InlineIrTypeView::Untagged(path, view) => {
58                    CodegenUntagged::new(CodegenTypeName::Inline(path), &view).into_token_stream()
59                }
60            });
61        let fields_module = inlines.next().map(|head| {
62            quote! {
63                pub mod types {
64                    #head
65                    #(#inlines)*
66                }
67            }
68        });
69
70        tokens.append_all(quote! {
71            #[cfg(feature = #feature_name)]
72            impl crate::client::Client {
73                #(#methods)*
74            }
75            #fields_module
76        });
77    }
78}
79
80impl IntoCode for CodegenResource<'_> {
81    type Code = (String, TokenStream);
82
83    fn into_code(self) -> Self::Code {
84        (
85            format!("src/client/{}.rs", self.resource.to_snake_case()),
86            self.into_token_stream(),
87        )
88    }
89}