ploidy_codegen_rust/
resource.rs1use ploidy_core::{codegen::IntoCode, ir::IrOperationView};
2use proc_macro2::TokenStream;
3use quote::{ToTokens, TokenStreamExt, quote};
4
5use super::{
6 cfg::CfgFeature,
7 inlines::CodegenInlines,
8 naming::{CargoFeature, CodegenIdentUsage},
9 operation::CodegenOperation,
10};
11
12pub struct CodegenResource<'a> {
15 feature: &'a CargoFeature,
16 ops: &'a [IrOperationView<'a>],
17}
18
19impl<'a> CodegenResource<'a> {
20 pub fn new(feature: &'a CargoFeature, ops: &'a [IrOperationView<'a>]) -> Self {
21 Self { feature, ops }
22 }
23}
24
25impl ToTokens for CodegenResource<'_> {
26 fn to_tokens(&self, tokens: &mut TokenStream) {
27 let methods = self.ops.iter().map(|view| {
29 let cfg = CfgFeature::for_operation(view);
30 let method = CodegenOperation::new(view).into_token_stream();
31 quote! {
32 #cfg
33 #method
34 }
35 });
36 let inlines = CodegenInlines::Resource(self.ops);
37 tokens.append_all(quote! {
38 impl crate::client::Client {
39 #(#methods)*
40 }
41 #inlines
42 });
43 }
44}
45
46impl IntoCode for CodegenResource<'_> {
47 type Code = (String, TokenStream);
48
49 fn into_code(self) -> Self::Code {
50 (
51 format!(
52 "src/client/{}.rs",
53 CodegenIdentUsage::Module(self.feature.as_ident()).display()
54 ),
55 self.into_token_stream(),
56 )
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63
64 use itertools::Itertools;
65 use ploidy_core::{
66 ir::{IrGraph, IrSpec},
67 parse::Document,
68 };
69 use pretty_assertions::assert_eq;
70
71 use syn::parse_quote;
72
73 use crate::{graph::CodegenGraph, naming::CargoFeature};
74
75 #[test]
76 fn test_operation_method_with_only_unnamed_deps_has_no_cfg() {
77 let doc = Document::from_yaml(indoc::indoc! {"
78 openapi: 3.0.0
79 info:
80 title: Test
81 version: 1.0.0
82 paths:
83 /customers:
84 get:
85 operationId: listCustomers
86 x-resource-name: customer
87 responses:
88 '200':
89 description: OK
90 content:
91 application/json:
92 schema:
93 type: array
94 items:
95 $ref: '#/components/schemas/Customer'
96 components:
97 schemas:
98 Customer:
99 type: object
100 properties:
101 address:
102 $ref: '#/components/schemas/Address'
103 Address:
104 type: object
105 properties:
106 street:
107 type: string
108 "})
109 .unwrap();
110
111 let spec = IrSpec::from_doc(&doc).unwrap();
112 let ir = IrGraph::new(&spec);
113 let graph = CodegenGraph::new(ir);
114
115 let ops = graph.operations().collect_vec();
116 let feature = CargoFeature::from_name("customer");
117 let resource = CodegenResource::new(&feature, &ops);
118
119 let actual: syn::File = parse_quote!(#resource);
122 let block = actual
123 .items
124 .iter()
125 .find_map(|item| match item {
126 syn::Item::Impl(block) => Some(block),
127 _ => None,
128 })
129 .unwrap();
130
131 let methods = block
134 .items
135 .iter()
136 .filter_map(|item| match item {
137 syn::ImplItem::Fn(method) => Some(method),
138 _ => None,
139 })
140 .collect_vec();
141 assert_eq!(methods.len(), 1);
142 assert!(
143 !methods[0]
144 .attrs
145 .iter()
146 .any(|attr| attr.path().is_ident("cfg"))
147 );
148 }
149
150 #[test]
151 fn test_operation_method_with_named_deps_has_cfg() {
152 let doc = Document::from_yaml(indoc::indoc! {"
153 openapi: 3.0.0
154 info:
155 title: Test
156 version: 1.0.0
157 paths:
158 /orders:
159 get:
160 operationId: listOrders
161 x-resource-name: orders
162 responses:
163 '200':
164 description: OK
165 content:
166 application/json:
167 schema:
168 type: array
169 items:
170 $ref: '#/components/schemas/Order'
171 components:
172 schemas:
173 Order:
174 type: object
175 properties:
176 customer:
177 $ref: '#/components/schemas/Customer'
178 Customer:
179 type: object
180 x-resourceId: customer
181 properties:
182 id:
183 type: string
184 "})
185 .unwrap();
186
187 let spec = IrSpec::from_doc(&doc).unwrap();
188 let ir = IrGraph::new(&spec);
189 let graph = CodegenGraph::new(ir);
190
191 let ops = graph.operations().collect_vec();
192 let feature = CargoFeature::from_name("orders");
193 let resource = CodegenResource::new(&feature, &ops);
194
195 let actual: syn::File = parse_quote!(#resource);
196 let block = actual
197 .items
198 .iter()
199 .find_map(|item| match item {
200 syn::Item::Impl(block) => Some(block),
201 _ => None,
202 })
203 .unwrap();
204
205 let methods = block
208 .items
209 .iter()
210 .filter_map(|item| match item {
211 syn::ImplItem::Fn(method) => Some(method),
212 _ => None,
213 })
214 .collect_vec();
215 assert_eq!(methods.len(), 1);
216 let cfg = methods[0]
217 .attrs
218 .iter()
219 .find(|attr| attr.path().is_ident("cfg"));
220 let expected: syn::Attribute = parse_quote!(#[cfg(feature = "customer")]);
221 assert_eq!(cfg, Some(&expected));
222 }
223}