1use itertools::Itertools;
2use ploidy_core::{
3 codegen::UniqueNames,
4 ir::{OperationView, ParameterView, PathParameter, RequestView, ResponseView},
5 parse::{Method, path::PathFragment},
6};
7use proc_macro2::{Span, TokenStream};
8use quote::{ToTokens, TokenStreamExt, format_ident, quote};
9use syn::Ident;
10
11use super::{
12 doc_attrs,
13 naming::{CodegenIdent, CodegenIdentScope, CodegenIdentUsage},
14 ref_::CodegenRef,
15};
16
17pub struct CodegenOperation<'a> {
19 op: &'a OperationView<'a, 'a>,
20}
21
22impl<'a> CodegenOperation<'a> {
23 pub fn new(op: &'a OperationView<'a, 'a>) -> Self {
24 Self { op }
25 }
26
27 fn url(
30 &self,
31 params: &[(CodegenIdent, ParameterView<'_, '_, '_, PathParameter>)],
32 ) -> TokenStream {
33 let segments = self
34 .op
35 .path()
36 .segments()
37 .map(|segment| match segment.fragments() {
38 [] => quote! { "" },
39 [PathFragment::Literal(text)] => quote! { #text },
40 [PathFragment::Param(name)] => {
41 let (ident, _) = params
42 .iter()
43 .find(|(_, param)| param.name() == *name)
44 .unwrap();
45 let usage = CodegenIdentUsage::Param(ident);
46 quote!(#usage)
47 }
48 fragments => {
49 let format = fragments.iter().fold(String::new(), |mut f, fragment| {
51 match fragment {
52 PathFragment::Literal(text) => {
53 f.push_str(&text.replace('{', "{{").replace('}', "}}"))
54 }
55 PathFragment::Param(_) => f.push_str("{}"),
56 }
57 f
58 });
59 let args = fragments
60 .iter()
61 .filter_map(|fragment| match fragment {
62 PathFragment::Param(name) => Some(name),
63 PathFragment::Literal(_) => None,
64 })
65 .map(|name| {
66 let (ident, _) = params
70 .iter()
71 .find(|(_, param)| param.name() == *name)
72 .unwrap();
73 CodegenIdentUsage::Param(ident)
74 });
75 quote! { &format!(#format, #(#args),*) }
76 }
77 });
78 let query = self
79 .op
80 .path()
81 .query()
82 .map(|param| {
83 let name = param.name;
84 let value = param.value;
85 quote! { .append_pair(#name, #value) }
86 })
87 .reduce(|a, b| quote!(#a #b))
88 .map(|pairs| {
89 quote! {
90 url.query_pairs_mut()
91 #pairs;
92 }
93 });
94
95 quote! {
96 let url = {
97 let mut url = self.base_url.clone();
98 let _ = url
99 .path_segments_mut()
100 .map(|mut segments| {
101 segments.pop_if_empty()
102 #(.push(#segments))*;
103 });
104 #query
105 url
106 };
107 }
108 }
109
110 fn query(&self) -> Option<TokenStream> {
112 self.op.query().next().is_some().then(|| {
113 let op_ident = CodegenIdent::new(self.op.id());
114 let query_name = format_ident!("{}Query", CodegenIdentUsage::Type(&op_ident));
115 quote! {
116 let url = ::ploidy_util::serde::Serialize::serialize(
117 query,
118 ::ploidy_util::QuerySerializer::new(
119 url,
120 parameters::#query_name::STYLES,
121 ),
122 )?;
123 }
124 })
125 }
126}
127
128impl ToTokens for CodegenOperation<'_> {
129 fn to_tokens(&self, tokens: &mut TokenStream) {
130 let operation_id = CodegenIdent::new(self.op.id());
131 let method_name = CodegenIdentUsage::Method(&operation_id);
132
133 let unique = UniqueNames::new();
134 let mut scope = CodegenIdentScope::with_reserved(
135 &unique,
136 &["query", "request", "form", "url", "response"],
139 );
140 let mut params = vec![];
141
142 let paths = self
143 .op
144 .path()
145 .params()
146 .map(|param| (scope.uniquify(param.name()), param))
147 .collect_vec();
148 for (ident, _) in &paths {
149 let usage = CodegenIdentUsage::Param(ident);
150 params.push(quote! { #usage: &str });
151 }
152
153 if self.op.query().next().is_some() {
154 let op_ident = CodegenIdent::new(self.op.id());
157 let query_type_name = format_ident!("{}Query", CodegenIdentUsage::Type(&op_ident));
158 params.push(quote! { query: ¶meters::#query_type_name });
159 }
160
161 if let Some(request) = self.op.request() {
162 match request {
163 RequestView::Json(view) => {
164 let param_type = CodegenRef::new(&view);
165 params.push(quote! { request: impl Into<#param_type> });
166 }
167 RequestView::Multipart => {
168 params.push(quote! { form: crate::util::reqwest::multipart::Form });
169 }
170 }
171 }
172
173 let return_type = match self.op.response() {
174 Some(response) => match response {
175 ResponseView::Json(view) => CodegenRef::new(&view).into_token_stream(),
176 },
177 None => quote! { () },
178 };
179
180 let build_url = self.url(&paths);
181
182 let build_query = self.query();
183
184 let http_method = CodegenMethod(self.op.method());
185
186 let build_request = match self.op.request() {
187 Some(RequestView::Json(_)) => quote! {
188 let response = self.client
189 .#http_method(url)
190 .headers(self.headers.clone())
191 .json(&request.into())
192 .send()
193 .await?
194 .error_for_status()?;
195 },
196 Some(RequestView::Multipart) => quote! {
197 let response = self.client
198 .#http_method(url)
199 .headers(self.headers.clone())
200 .multipart(form)
201 .send()
202 .await?
203 .error_for_status()?;
204 },
205 None => quote! {
206 let response = self.client
207 .#http_method(url)
208 .headers(self.headers.clone())
209 .send()
210 .await?
211 .error_for_status()?;
212 },
213 };
214
215 let parse_response = if self.op.response().is_some() {
216 quote! {
217 let body = response.bytes().await?;
218 let deserializer = &mut ::ploidy_util::serde_json::Deserializer::from_slice(&body);
219 let result = ::ploidy_util::serde_path_to_error::deserialize(deserializer)
220 .map_err(crate::error::JsonError::from)?;
221 Ok(result)
222 }
223 } else {
224 quote! {
225 let _ = response;
226 Ok(())
227 }
228 };
229
230 let doc = self.op.description().map(doc_attrs);
231
232 tokens.append_all(quote! {
233 #doc
234 pub async fn #method_name(
235 &self,
236 #(#params),*
237 ) -> Result<#return_type, crate::error::Error> {
238 #build_url
239 #build_query
240 #build_request
241 #parse_response
242 }
243 });
244 }
245}
246
247#[derive(Clone, Copy, Debug)]
248pub struct CodegenMethod(pub Method);
249
250impl ToTokens for CodegenMethod {
251 fn to_tokens(&self, tokens: &mut TokenStream) {
252 tokens.append(match self.0 {
253 Method::Get => Ident::new("get", Span::call_site()),
254 Method::Post => Ident::new("post", Span::call_site()),
255 Method::Put => Ident::new("put", Span::call_site()),
256 Method::Patch => Ident::new("patch", Span::call_site()),
257 Method::Delete => Ident::new("delete", Span::call_site()),
258 });
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265
266 use ploidy_core::{
267 arena::Arena,
268 ir::{RawGraph, Spec},
269 parse::Document,
270 };
271 use pretty_assertions::assert_eq;
272 use syn::parse_quote;
273
274 use crate::CodegenGraph;
275
276 #[test]
279 fn test_operation_with_path_and_query_params() {
280 let doc = Document::from_yaml(indoc::indoc! {"
281 openapi: 3.0.0
282 info:
283 title: Test API
284 version: 1.0.0
285 paths:
286 /items/{item_id}:
287 get:
288 operationId: getItem
289 parameters:
290 - name: item_id
291 in: path
292 required: true
293 schema:
294 type: string
295 - name: expand
296 in: query
297 schema:
298 type: boolean
299 responses:
300 '200':
301 description: OK
302 "})
303 .unwrap();
304
305 let arena = Arena::new();
306 let spec = Spec::from_doc(&arena, &doc).unwrap();
307 let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
308
309 let op = graph.operations().next().unwrap();
310 let codegen = CodegenOperation::new(&op);
311
312 let actual: syn::ImplItemFn = parse_quote!(#codegen);
313 let expected: syn::ImplItemFn = parse_quote! {
314 pub async fn get_item(
315 &self,
316 item_id: &str,
317 query: ¶meters::GetItemQuery
318 ) -> Result<(), crate::error::Error> {
319 let url = {
320 let mut url = self.base_url.clone();
321 let _ = url
322 .path_segments_mut()
323 .map(|mut segments| {
324 segments.pop_if_empty()
325 .push("items")
326 .push(item_id);
327 });
328 url
329 };
330 let url = ::ploidy_util::serde::Serialize::serialize(
331 query,
332 ::ploidy_util::QuerySerializer::new(
333 url,
334 parameters::GetItemQuery::STYLES,
335 ),
336 )?;
337 let response = self
338 .client
339 .get(url)
340 .headers(self.headers.clone())
341 .send()
342 .await?
343 .error_for_status()?;
344 let _ = response;
345 Ok(())
346 }
347 };
348 assert_eq!(actual, expected);
349 }
350
351 #[test]
352 fn test_operation_with_query_params_only() {
353 let doc = Document::from_yaml(indoc::indoc! {"
354 openapi: 3.0.0
355 info:
356 title: Test API
357 version: 1.0.0
358 paths:
359 /items:
360 get:
361 operationId: getItems
362 parameters:
363 - name: limit
364 in: query
365 schema:
366 type: integer
367 format: int32
368 responses:
369 '200':
370 description: OK
371 "})
372 .unwrap();
373
374 let arena = Arena::new();
375 let spec = Spec::from_doc(&arena, &doc).unwrap();
376 let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
377
378 let op = graph.operations().next().unwrap();
379 let codegen = CodegenOperation::new(&op);
380
381 let actual: syn::ImplItemFn = parse_quote!(#codegen);
382 let expected: syn::ImplItemFn = parse_quote! {
383 pub async fn get_items(
384 &self,
385 query: ¶meters::GetItemsQuery
386 ) -> Result<(), crate::error::Error> {
387 let url = {
388 let mut url = self.base_url.clone();
389 let _ = url
390 .path_segments_mut()
391 .map(|mut segments| {
392 segments.pop_if_empty()
393 .push("items");
394 });
395 url
396 };
397 let url = ::ploidy_util::serde::Serialize::serialize(
398 query,
399 ::ploidy_util::QuerySerializer::new(
400 url,
401 parameters::GetItemsQuery::STYLES,
402 ),
403 )?;
404 let response = self
405 .client
406 .get(url)
407 .headers(self.headers.clone())
408 .send()
409 .await?
410 .error_for_status()?;
411 let _ = response;
412 Ok(())
413 }
414 };
415 assert_eq!(actual, expected);
416 }
417
418 #[test]
419 fn test_path_param_named_query_does_not_shadow() {
420 let doc = Document::from_yaml(indoc::indoc! {"
421 openapi: 3.0.0
422 info:
423 title: Test API
424 version: 1.0.0
425 paths:
426 /search/{query}:
427 get:
428 operationId: search
429 parameters:
430 - name: query
431 in: path
432 required: true
433 schema:
434 type: string
435 - name: limit
436 in: query
437 schema:
438 type: integer
439 format: int32
440 responses:
441 '200':
442 description: OK
443 "})
444 .unwrap();
445
446 let arena = Arena::new();
447 let spec = Spec::from_doc(&arena, &doc).unwrap();
448 let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
449
450 let op = graph.operations().next().unwrap();
451 let codegen = CodegenOperation::new(&op);
452
453 let actual: syn::ImplItemFn = parse_quote!(#codegen);
454 let expected: syn::ImplItemFn = parse_quote! {
455 pub async fn search(
456 &self,
457 query2: &str,
458 query: ¶meters::SearchQuery
459 ) -> Result<(), crate::error::Error> {
460 let url = {
461 let mut url = self.base_url.clone();
462 let _ = url
463 .path_segments_mut()
464 .map(|mut segments| {
465 segments.pop_if_empty()
466 .push("search")
467 .push(query2);
468 });
469 url
470 };
471 let url = ::ploidy_util::serde::Serialize::serialize(
472 query,
473 ::ploidy_util::QuerySerializer::new(
474 url,
475 parameters::SearchQuery::STYLES,
476 ),
477 )?;
478 let response = self
479 .client
480 .get(url)
481 .headers(self.headers.clone())
482 .send()
483 .await?
484 .error_for_status()?;
485 let _ = response;
486 Ok(())
487 }
488 };
489 assert_eq!(actual, expected);
490 }
491
492 #[test]
495 fn test_operation_with_query_params_and_request_body() {
496 let doc = Document::from_yaml(indoc::indoc! {"
497 openapi: 3.0.0
498 info:
499 title: Test API
500 version: 1.0.0
501 paths:
502 /items/{item_id}:
503 put:
504 operationId: updateItem
505 parameters:
506 - name: item_id
507 in: path
508 required: true
509 schema:
510 type: string
511 - name: dry_run
512 in: query
513 schema:
514 type: boolean
515 requestBody:
516 content:
517 application/json:
518 schema:
519 $ref: '#/components/schemas/Item'
520 responses:
521 '200':
522 description: OK
523 content:
524 application/json:
525 schema:
526 $ref: '#/components/schemas/Item'
527 components:
528 schemas:
529 Item:
530 type: object
531 properties:
532 name:
533 type: string
534 "})
535 .unwrap();
536
537 let arena = Arena::new();
538 let spec = Spec::from_doc(&arena, &doc).unwrap();
539 let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
540
541 let op = graph.operations().next().unwrap();
542 let codegen = CodegenOperation::new(&op);
543
544 let actual: syn::ImplItemFn = parse_quote!(#codegen);
545 let expected: syn::ImplItemFn = parse_quote! {
546 pub async fn update_item(
547 &self,
548 item_id: &str,
549 query: ¶meters::UpdateItemQuery,
550 request: impl Into<crate::types::Item>
551 ) -> Result<crate::types::Item, crate::error::Error> {
552 let url = {
553 let mut url = self.base_url.clone();
554 let _ = url
555 .path_segments_mut()
556 .map(|mut segments| {
557 segments.pop_if_empty()
558 .push("items")
559 .push(item_id);
560 });
561 url
562 };
563 let url = ::ploidy_util::serde::Serialize::serialize(
564 query,
565 ::ploidy_util::QuerySerializer::new(
566 url,
567 parameters::UpdateItemQuery::STYLES,
568 ),
569 )?;
570 let response = self
571 .client
572 .put(url)
573 .headers(self.headers.clone())
574 .json(&request.into())
575 .send()
576 .await?
577 .error_for_status()?;
578 let body = response.bytes().await?;
579 let deserializer = &mut ::ploidy_util::serde_json::Deserializer::from_slice(&body);
580 let result = ::ploidy_util::serde_path_to_error::deserialize(deserializer)
581 .map_err(crate::error::JsonError::from)?;
582 Ok(result)
583 }
584 };
585 assert_eq!(actual, expected);
586 }
587
588 #[test]
591 fn test_operation_without_query_params() {
592 let doc = Document::from_yaml(indoc::indoc! {"
593 openapi: 3.0.0
594 info:
595 title: Test API
596 version: 1.0.0
597 paths:
598 /items/{item_id}:
599 get:
600 operationId: getItem
601 parameters:
602 - name: item_id
603 in: path
604 required: true
605 schema:
606 type: string
607 responses:
608 '200':
609 description: OK
610 "})
611 .unwrap();
612
613 let arena = Arena::new();
614 let spec = Spec::from_doc(&arena, &doc).unwrap();
615 let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
616
617 let op = graph.operations().next().unwrap();
618 let codegen = CodegenOperation::new(&op);
619
620 let actual: syn::ImplItemFn = parse_quote!(#codegen);
621 let expected: syn::ImplItemFn = parse_quote! {
622 pub async fn get_item(
623 &self,
624 item_id: &str
625 ) -> Result<(), crate::error::Error> {
626 let url = {
627 let mut url = self.base_url.clone();
628 let _ = url
629 .path_segments_mut()
630 .map(|mut segments| {
631 segments.pop_if_empty()
632 .push("items")
633 .push(item_id);
634 });
635 url
636 };
637 let response = self
638 .client
639 .get(url)
640 .headers(self.headers.clone())
641 .send()
642 .await?
643 .error_for_status()?;
644 let _ = response;
645 Ok(())
646 }
647 };
648 assert_eq!(actual, expected);
649 }
650
651 #[test]
654 fn test_operation_with_synthesized_path_param() {
655 let doc = Document::from_yaml(indoc::indoc! {"
656 openapi: 3.0.0
657 info:
658 title: Test API
659 version: 1.0.0
660 paths:
661 /items/{item_id}:
662 get:
663 operationId: getItem
664 responses:
665 '200':
666 description: OK
667 "})
668 .unwrap();
669
670 let arena = Arena::new();
671 let spec = Spec::from_doc(&arena, &doc).unwrap();
672 let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
673
674 let op = graph.operations().next().unwrap();
675 let codegen = CodegenOperation::new(&op);
676
677 let actual: syn::ImplItemFn = parse_quote!(#codegen);
678 let expected: syn::ImplItemFn = parse_quote! {
679 pub async fn get_item(
680 &self,
681 item_id: &str
682 ) -> Result<(), crate::error::Error> {
683 let url = {
684 let mut url = self.base_url.clone();
685 let _ = url
686 .path_segments_mut()
687 .map(|mut segments| {
688 segments.pop_if_empty()
689 .push("items")
690 .push(item_id);
691 });
692 url
693 };
694 let response = self
695 .client
696 .get(url)
697 .headers(self.headers.clone())
698 .send()
699 .await?
700 .error_for_status()?;
701 let _ = response;
702 Ok(())
703 }
704 };
705 assert_eq!(actual, expected);
706 }
707
708 #[test]
711 fn test_operation_with_literal_query_params() {
712 let doc = Document::from_yaml(indoc::indoc! {"
713 openapi: 3.0.0
714 info:
715 title: Test API
716 version: 1.0.0
717 paths:
718 /v1/messages?beta=true&expand:
719 post:
720 operationId: betaCreateMessage
721 requestBody:
722 content:
723 application/json:
724 schema:
725 $ref: '#/components/schemas/Message'
726 responses:
727 '200':
728 description: OK
729 content:
730 application/json:
731 schema:
732 $ref: '#/components/schemas/Message'
733 components:
734 schemas:
735 Message:
736 type: object
737 properties:
738 content:
739 type: string
740 "})
741 .unwrap();
742
743 let arena = Arena::new();
744 let spec = Spec::from_doc(&arena, &doc).unwrap();
745 let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
746
747 let op = graph.operations().next().unwrap();
748 let codegen = CodegenOperation::new(&op);
749
750 let actual: syn::ImplItemFn = parse_quote!(#codegen);
751 let expected: syn::ImplItemFn = parse_quote! {
752 pub async fn beta_create_message(
753 &self,
754 request: impl Into<crate::types::Message>
755 ) -> Result<crate::types::Message, crate::error::Error> {
756 let url = {
757 let mut url = self.base_url.clone();
758 let _ = url
759 .path_segments_mut()
760 .map(|mut segments| {
761 segments.pop_if_empty()
762 .push("v1")
763 .push("messages");
764 });
765 url.query_pairs_mut()
766 .append_pair("beta", "true")
767 .append_pair("expand", "");
768 url
769 };
770 let response = self
771 .client
772 .post(url)
773 .headers(self.headers.clone())
774 .json(&request.into())
775 .send()
776 .await?
777 .error_for_status()?;
778 let body = response.bytes().await?;
779 let deserializer = &mut ::ploidy_util::serde_json::Deserializer::from_slice(&body);
780 let result = ::ploidy_util::serde_path_to_error::deserialize(deserializer)
781 .map_err(crate::error::JsonError::from)?;
782 Ok(result)
783 }
784 };
785 assert_eq!(actual, expected);
786 }
787
788 #[test]
789 fn test_operation_with_literal_and_declared_query_params() {
790 let doc = Document::from_yaml(indoc::indoc! {"
791 openapi: 3.0.0
792 info:
793 title: Test API
794 version: 1.0.0
795 paths:
796 /v1/messages?beta=true:
797 post:
798 operationId: betaCreateMessage
799 parameters:
800 - name: limit
801 in: query
802 schema:
803 type: integer
804 format: int32
805 requestBody:
806 content:
807 application/json:
808 schema:
809 $ref: '#/components/schemas/Message'
810 responses:
811 '200':
812 description: OK
813 content:
814 application/json:
815 schema:
816 $ref: '#/components/schemas/Message'
817 components:
818 schemas:
819 Message:
820 type: object
821 properties:
822 content:
823 type: string
824 "})
825 .unwrap();
826
827 let arena = Arena::new();
828 let spec = Spec::from_doc(&arena, &doc).unwrap();
829 let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
830
831 let op = graph.operations().next().unwrap();
832 let codegen = CodegenOperation::new(&op);
833
834 let actual: syn::ImplItemFn = parse_quote!(#codegen);
835 let expected: syn::ImplItemFn = parse_quote! {
836 pub async fn beta_create_message(
837 &self,
838 query: ¶meters::BetaCreateMessageQuery,
839 request: impl Into<crate::types::Message>
840 ) -> Result<crate::types::Message, crate::error::Error> {
841 let url = {
842 let mut url = self.base_url.clone();
843 let _ = url
844 .path_segments_mut()
845 .map(|mut segments| {
846 segments.pop_if_empty()
847 .push("v1")
848 .push("messages");
849 });
850 url.query_pairs_mut()
851 .append_pair("beta", "true");
852 url
853 };
854 let url = ::ploidy_util::serde::Serialize::serialize(
855 query,
856 ::ploidy_util::QuerySerializer::new(
857 url,
858 parameters::BetaCreateMessageQuery::STYLES,
859 ),
860 )?;
861 let response = self
862 .client
863 .post(url)
864 .headers(self.headers.clone())
865 .json(&request.into())
866 .send()
867 .await?
868 .error_for_status()?;
869 let body = response.bytes().await?;
870 let deserializer = &mut ::ploidy_util::serde_json::Deserializer::from_slice(&body);
871 let result = ::ploidy_util::serde_path_to_error::deserialize(deserializer)
872 .map_err(crate::error::JsonError::from)?;
873 Ok(result)
874 }
875 };
876 assert_eq!(actual, expected);
877 }
878
879 #[test]
880 fn test_operation_with_path_params_and_literal_and_declared_query_params() {
881 let doc = Document::from_yaml(indoc::indoc! {"
882 openapi: 3.0.0
883 info:
884 title: Test API
885 version: 1.0.0
886 paths:
887 /v1/models/{model_id}?beta=true:
888 get:
889 operationId: betaGetModel
890 parameters:
891 - name: model_id
892 in: path
893 required: true
894 schema:
895 type: string
896 - name: expand
897 in: query
898 schema:
899 type: boolean
900 responses:
901 '200':
902 description: OK
903 content:
904 application/json:
905 schema:
906 $ref: '#/components/schemas/Model'
907 components:
908 schemas:
909 Model:
910 type: object
911 properties:
912 id:
913 type: string
914 "})
915 .unwrap();
916
917 let arena = Arena::new();
918 let spec = Spec::from_doc(&arena, &doc).unwrap();
919 let graph = CodegenGraph::new(RawGraph::new(&arena, &spec).cook());
920
921 let op = graph.operations().next().unwrap();
922 let codegen = CodegenOperation::new(&op);
923
924 let actual: syn::ImplItemFn = parse_quote!(#codegen);
925 let expected: syn::ImplItemFn = parse_quote! {
926 pub async fn beta_get_model(
927 &self,
928 model_id: &str,
929 query: ¶meters::BetaGetModelQuery
930 ) -> Result<crate::types::Model, crate::error::Error> {
931 let url = {
932 let mut url = self.base_url.clone();
933 let _ = url
934 .path_segments_mut()
935 .map(|mut segments| {
936 segments.pop_if_empty()
937 .push("v1")
938 .push("models")
939 .push(model_id);
940 });
941 url.query_pairs_mut()
942 .append_pair("beta", "true");
943 url
944 };
945 let url = ::ploidy_util::serde::Serialize::serialize(
946 query,
947 ::ploidy_util::QuerySerializer::new(
948 url,
949 parameters::BetaGetModelQuery::STYLES,
950 ),
951 )?;
952 let response = self
953 .client
954 .get(url)
955 .headers(self.headers.clone())
956 .send()
957 .await?
958 .error_for_status()?;
959 let body = response.bytes().await?;
960 let deserializer = &mut ::ploidy_util::serde_json::Deserializer::from_slice(&body);
961 let result = ::ploidy_util::serde_path_to_error::deserialize(deserializer)
962 .map_err(crate::error::JsonError::from)?;
963 Ok(result)
964 }
965 };
966 assert_eq!(actual, expected);
967 }
968}