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