1use std::{
4 cmp::Ordering,
5 collections::{BTreeMap, BTreeSet},
6 str::FromStr,
7};
8
9use openapiv3::{Components, Parameter, ReferenceOr, Response, StatusCode};
10use proc_macro2::TokenStream;
11use quote::{format_ident, quote, ToTokens};
12use typify::{TypeId, TypeSpace};
13
14use crate::{
15 template::PathTemplate,
16 util::{items, parameter_map, sanitize, unique_ident_from, Case},
17 Error, Generator, Result, TagStyle,
18};
19use crate::{to_schema::ToSchema, util::ReferenceOrExt};
20
21pub(crate) struct OperationMethod {
23 pub operation_id: String,
24 pub tags: Vec<String>,
25 pub method: HttpMethod,
26 pub path: PathTemplate,
27 pub summary: Option<String>,
28 pub description: Option<String>,
29 pub params: Vec<OperationParameter>,
30 pub responses: Vec<OperationResponse>,
31 pub dropshot_paginated: Option<DropshotPagination>,
32 dropshot_websocket: bool,
33 pub error_enum_name: Option<String>,
34}
35
36#[derive(Debug, Clone)]
39pub enum ErrorResponseType {
40 Single(OperationResponseKind),
42 Multiple {
44 enum_name: String,
45 variants: Vec<ErrorVariant>,
46 },
47}
48
49#[derive(Debug, Clone)]
51pub struct ErrorVariant {
52 pub variant_name: String,
53 pub typ: OperationResponseKind,
54 pub status_codes: Vec<OperationResponseStatus>,
55}
56
57pub enum HttpMethod {
58 Get,
59 Put,
60 Post,
61 Delete,
62 Options,
63 Head,
64 Patch,
65 Trace,
66}
67
68impl std::str::FromStr for HttpMethod {
69 type Err = Error;
70
71 fn from_str(s: &str) -> Result<Self> {
72 match s {
73 "get" => Ok(Self::Get),
74 "put" => Ok(Self::Put),
75 "post" => Ok(Self::Post),
76 "delete" => Ok(Self::Delete),
77 "options" => Ok(Self::Options),
78 "head" => Ok(Self::Head),
79 "patch" => Ok(Self::Patch),
80 "trace" => Ok(Self::Trace),
81 _ => Err(Error::InternalError(format!("bad method: {}", s))),
82 }
83 }
84}
85impl HttpMethod {
86 fn as_str(&self) -> &'static str {
87 match self {
88 HttpMethod::Get => "get",
89 HttpMethod::Put => "put",
90 HttpMethod::Post => "post",
91 HttpMethod::Delete => "delete",
92 HttpMethod::Options => "options",
93 HttpMethod::Head => "head",
94 HttpMethod::Patch => "patch",
95 HttpMethod::Trace => "trace",
96 }
97 }
98}
99
100struct MethodSigBody {
101 success: TokenStream,
102 error: TokenStream,
103 body: TokenStream,
104}
105
106struct BuilderImpl {
107 doc: String,
108 sig: TokenStream,
109 body: TokenStream,
110}
111
112pub struct DropshotPagination {
113 pub item: TypeId,
114 pub first_page_params: Vec<String>,
115}
116
117pub struct OperationParameter {
118 pub name: String,
120 pub api_name: String,
122 pub description: Option<String>,
123 pub typ: OperationParameterType,
124 pub kind: OperationParameterKind,
125}
126
127#[derive(Eq, PartialEq)]
128pub enum OperationParameterType {
129 Type(TypeId),
130 RawBody,
131}
132
133#[derive(Debug, PartialEq, Eq)]
134pub enum OperationParameterKind {
135 Path,
136 Query(bool),
137 Header(bool),
138 Body(BodyContentType),
140}
141
142impl OperationParameterKind {
143 fn is_required(&self) -> bool {
144 match self {
145 OperationParameterKind::Path => true,
146 OperationParameterKind::Query(required) => *required,
147 OperationParameterKind::Header(required) => *required,
148 OperationParameterKind::Body(_) => true,
150 }
151 }
152 fn is_optional(&self) -> bool {
153 !self.is_required()
154 }
155}
156
157#[derive(Debug, PartialEq, Eq)]
158pub enum BodyContentType {
159 OctetStream,
160 Json,
161 FormUrlencoded,
162 Text(String),
163}
164
165impl FromStr for BodyContentType {
166 type Err = Error;
167
168 fn from_str(s: &str) -> Result<Self> {
169 let offset = s.find(';').unwrap_or(s.len());
170 match &s[..offset] {
171 "application/octet-stream" => Ok(Self::OctetStream),
172 "application/json" => Ok(Self::Json),
173 "application/x-www-form-urlencoded" => Ok(Self::FormUrlencoded),
174 "text/plain" | "text/x-markdown" => Ok(Self::Text(String::from(&s[..offset]))),
175 _ => Err(Error::UnexpectedFormat(format!(
176 "unexpected content type: {}",
177 s
178 ))),
179 }
180 }
181}
182
183impl std::fmt::Display for BodyContentType {
184 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185 f.write_str(match self {
186 Self::OctetStream => "application/octet-stream",
187 Self::Json => "application/json",
188 Self::FormUrlencoded => "application/x-www-form-urlencoded",
189 Self::Text(typ) => typ,
190 })
191 }
192}
193
194#[derive(Debug)]
195pub(crate) struct OperationResponse {
196 pub status_code: OperationResponseStatus,
197 pub typ: OperationResponseKind,
198 #[allow(dead_code)]
201 description: Option<String>,
202}
203
204impl Eq for OperationResponse {}
205impl PartialEq for OperationResponse {
206 fn eq(&self, other: &Self) -> bool {
207 self.status_code == other.status_code
208 }
209}
210impl Ord for OperationResponse {
211 fn cmp(&self, other: &Self) -> Ordering {
212 self.status_code.cmp(&other.status_code)
213 }
214}
215impl PartialOrd for OperationResponse {
216 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
217 Some(self.cmp(other))
218 }
219}
220
221#[derive(Debug, Clone, Eq, PartialEq)]
222pub(crate) enum OperationResponseStatus {
223 Code(u16),
224 Range(u16),
225 Default,
226}
227
228impl OperationResponseStatus {
229 fn to_value(&self) -> u16 {
230 match self {
231 OperationResponseStatus::Code(code) => {
232 assert!(*code < 1000);
233 *code
234 }
235 OperationResponseStatus::Range(range) => {
236 assert!(*range < 10);
237 *range * 100
238 }
239 OperationResponseStatus::Default => 1000,
240 }
241 }
242
243 pub fn is_success_or_default(&self) -> bool {
244 matches!(
245 self,
246 OperationResponseStatus::Default
247 | OperationResponseStatus::Code(101)
248 | OperationResponseStatus::Code(200..=299)
249 | OperationResponseStatus::Range(2)
250 )
251 }
252
253 pub fn is_error_or_default(&self) -> bool {
254 matches!(
255 self,
256 OperationResponseStatus::Default
257 | OperationResponseStatus::Code(400..=599)
258 | OperationResponseStatus::Range(4..=5)
259 )
260 }
261
262 pub fn is_default(&self) -> bool {
263 matches!(self, OperationResponseStatus::Default)
264 }
265}
266
267impl Ord for OperationResponseStatus {
268 fn cmp(&self, other: &Self) -> Ordering {
269 self.to_value().cmp(&other.to_value())
270 }
271}
272
273impl PartialOrd for OperationResponseStatus {
274 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
275 Some(self.cmp(other))
276 }
277}
278
279#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
280pub(crate) enum OperationResponseKind {
281 Type(TypeId),
282 None,
283 Raw,
284 Upgrade,
285}
286
287impl OperationResponseKind {
288 pub fn into_tokens(self, type_space: &TypeSpace) -> TokenStream {
289 match self {
290 OperationResponseKind::Type(ref type_id) => {
291 let type_name = type_space.get_type(type_id).unwrap().ident();
292 quote! { #type_name }
293 }
294 OperationResponseKind::None => {
295 quote! { () }
296 }
297 OperationResponseKind::Raw => {
298 quote! { ByteStream }
299 }
300 OperationResponseKind::Upgrade => {
301 quote! { reqwest::Upgraded }
302 }
303 }
304 }
305}
306
307impl Generator {
308 pub(crate) fn process_operation(
309 &mut self,
310 operation: &openapiv3::Operation,
311 components: &Option<Components>,
312 path: &str,
313 method: &str,
314 path_parameters: &[ReferenceOr<Parameter>],
315 ) -> Result<OperationMethod> {
316 let operation_id = operation.operation_id.as_ref().unwrap();
317
318 let mut combined_path_parameters = parameter_map(path_parameters, components)?;
319 for operation_param in items(&operation.parameters, components) {
320 let parameter = operation_param?;
321 combined_path_parameters.insert(¶meter.parameter_data_ref().name, parameter);
322 }
323
324 let mut params = combined_path_parameters
327 .values()
328 .map(|parameter| {
329 match parameter {
330 openapiv3::Parameter::Path {
331 parameter_data,
332 style: openapiv3::PathStyle::Simple,
333 } => {
334 assert!(parameter_data.required);
336
337 let schema = parameter_data.schema()?.to_schema();
338
339 let name = sanitize(
340 &format!("{}-{}", operation_id, ¶meter_data.name),
341 Case::Pascal,
342 );
343 let typ = self.type_space.add_type_with_name(&schema, Some(name))?;
344
345 Ok(OperationParameter {
346 name: sanitize(¶meter_data.name, Case::Snake),
347 api_name: parameter_data.name.clone(),
348 description: parameter_data.description.clone(),
349 typ: OperationParameterType::Type(typ),
350 kind: OperationParameterKind::Path,
351 })
352 }
353 openapiv3::Parameter::Query {
354 parameter_data,
355 allow_reserved: _, style: openapiv3::QueryStyle::Form,
357 allow_empty_value: _, } => {
359 let schema = parameter_data.schema()?.to_schema();
360 let name = sanitize(
361 &format!(
362 "{}-{}",
363 operation.operation_id.as_ref().unwrap(),
364 ¶meter_data.name,
365 ),
366 Case::Pascal,
367 );
368
369 let type_id = self.type_space.add_type_with_name(&schema, Some(name))?;
370
371 let ty = self.type_space.get_type(&type_id).unwrap();
372
373 let details = ty.details();
377 let (type_id, required) =
378 if let typify::TypeDetails::Option(inner_type_id) = details {
379 (inner_type_id, false)
380 } else {
381 (type_id, parameter_data.required)
382 };
383
384 Ok(OperationParameter {
385 name: sanitize(¶meter_data.name, Case::Snake),
386 api_name: parameter_data.name.clone(),
387 description: parameter_data.description.clone(),
388 typ: OperationParameterType::Type(type_id),
389 kind: OperationParameterKind::Query(required),
390 })
391 }
392 openapiv3::Parameter::Header {
393 parameter_data,
394 style: openapiv3::HeaderStyle::Simple,
395 } => {
396 let schema = parameter_data.schema()?.to_schema();
397 let name = sanitize(
398 &format!(
399 "{}-{}",
400 operation.operation_id.as_ref().unwrap(),
401 ¶meter_data.name,
402 ),
403 Case::Pascal,
404 );
405
406 let typ = self.type_space.add_type_with_name(&schema, Some(name))?;
407
408 Ok(OperationParameter {
409 name: sanitize(¶meter_data.name, Case::Snake),
410 api_name: parameter_data.name.clone(),
411 description: parameter_data.description.clone(),
412 typ: OperationParameterType::Type(typ),
413 kind: OperationParameterKind::Header(parameter_data.required),
414 })
415 }
416 openapiv3::Parameter::Path { style, .. } => Err(Error::UnexpectedFormat(
417 format!("unsupported style of path parameter {:#?}", style,),
418 )),
419 openapiv3::Parameter::Query { style, .. } => Err(Error::UnexpectedFormat(
420 format!("unsupported style of query parameter {:#?}", style,),
421 )),
422 cookie @ openapiv3::Parameter::Cookie { .. } => Err(Error::UnexpectedFormat(
423 format!("cookie parameters are not supported {:#?}", cookie,),
424 )),
425 }
426 })
427 .collect::<Result<Vec<_>>>()?;
428
429 let dropshot_websocket = operation.extensions.get("x-dropshot-websocket").is_some();
430 if dropshot_websocket {
431 self.uses_websockets = true;
432 }
433
434 if let Some(body_param) = self.get_body_param(operation, components)? {
435 params.push(body_param);
436 }
437
438 let tmp = crate::template::parse(path)?;
439 let names = tmp.names();
440
441 sort_params(&mut params, &names);
442
443 let mut success = false;
444
445 let mut responses =
446 operation
447 .responses
448 .default
449 .iter()
450 .map(|response_or_ref| {
451 Ok((
452 OperationResponseStatus::Default,
453 response_or_ref.item(components)?,
454 ))
455 })
456 .chain(operation.responses.responses.iter().map(
457 |(status_code, response_or_ref)| {
458 Ok((
459 match status_code {
460 StatusCode::Code(code) => OperationResponseStatus::Code(*code),
461 StatusCode::Range(range) => OperationResponseStatus::Range(*range),
462 },
463 response_or_ref.item(components)?,
464 ))
465 },
466 ))
467 .map(|v: Result<(OperationResponseStatus, &Response)>| {
468 let (status_code, response) = v?;
469
470 let typ = if let Some(mt) = response.content.iter().find_map(|(x, v)| {
481 (x == "application/json" || x.starts_with("application/json;")).then_some(v)
482 }) {
483 assert!(mt.encoding.is_empty());
484
485 let typ = if let Some(schema) = &mt.schema {
486 let schema = schema.to_schema();
487 let name = sanitize(
488 &format!("{}-response", operation.operation_id.as_ref().unwrap(),),
489 Case::Pascal,
490 );
491 self.type_space.add_type_with_name(&schema, Some(name))?
492 } else {
493 todo!("media type encoding, no schema: {:#?}", mt);
494 };
495
496 OperationResponseKind::Type(typ)
497 } else if dropshot_websocket {
498 OperationResponseKind::Upgrade
499 } else if response.content.first().is_some() {
500 OperationResponseKind::Raw
501 } else {
502 OperationResponseKind::None
503 };
504
505 if matches!(
507 status_code,
508 OperationResponseStatus::Default
509 | OperationResponseStatus::Code(200..=299)
510 | OperationResponseStatus::Range(2)
511 ) {
512 success = true;
513 }
514
515 let description = if response.description.is_empty() {
516 None
517 } else {
518 Some(response.description.clone())
519 };
520
521 Ok(OperationResponse {
522 status_code,
523 typ,
524 description,
525 })
526 })
527 .collect::<Result<Vec<_>>>()?;
528
529 if !success {
534 responses.push(OperationResponse {
535 status_code: OperationResponseStatus::Range(2),
536 typ: OperationResponseKind::Raw,
537 description: None,
538 });
539 }
540
541 if dropshot_websocket {
543 responses.push(OperationResponse {
544 status_code: OperationResponseStatus::Code(101),
545 typ: OperationResponseKind::Upgrade,
546 description: None,
547 })
548 }
549
550 let dropshot_paginated = self.dropshot_pagination_data(operation, ¶ms, &responses);
551
552 if dropshot_websocket && dropshot_paginated.is_some() {
553 return Err(Error::InvalidExtension(format!(
554 "conflicting extensions in {:?}",
555 operation_id
556 )));
557 }
558
559 Ok(OperationMethod {
560 operation_id: sanitize(operation_id, Case::Snake),
561 tags: operation.tags.clone(),
562 method: HttpMethod::from_str(method)?,
563 path: tmp,
564 summary: operation.summary.clone().filter(|s| !s.is_empty()),
565 description: operation.description.clone().filter(|s| !s.is_empty()),
566 params,
567 responses,
568 dropshot_paginated,
569 dropshot_websocket,
570 error_enum_name: None, })
572 }
573
574 pub(crate) fn positional_method(
575 &mut self,
576 method: &OperationMethod,
577 has_inner: bool,
578 ) -> Result<TokenStream> {
579 let operation_id = format_ident!("{}", method.operation_id);
580
581 let params = method
583 .params
584 .iter()
585 .map(|param| {
586 let name = format_ident!("{}", param.name);
587 let typ = match (¶m.typ, param.kind.is_optional()) {
588 (OperationParameterType::Type(type_id), false) => self
589 .type_space
590 .get_type(type_id)
591 .unwrap()
592 .parameter_ident_with_lifetime("a"),
593 (OperationParameterType::Type(type_id), true) => {
594 let t = self
595 .type_space
596 .get_type(type_id)
597 .unwrap()
598 .parameter_ident_with_lifetime("a");
599 quote! { Option<#t> }
600 }
601 (OperationParameterType::RawBody, false) => match ¶m.kind {
602 OperationParameterKind::Body(BodyContentType::OctetStream) => {
603 quote! { B }
604 }
605 OperationParameterKind::Body(BodyContentType::Text(_)) => {
606 quote! { String }
607 }
608 _ => unreachable!(),
609 },
610 (OperationParameterType::RawBody, true) => unreachable!(),
611 };
612 quote! {
613 #name: #typ
614 }
615 })
616 .collect::<Vec<_>>();
617
618 let raw_body_param = method.params.iter().any(|param| {
619 param.typ == OperationParameterType::RawBody
620 && param.kind == OperationParameterKind::Body(BodyContentType::OctetStream)
621 });
622
623 let bounds = if raw_body_param {
624 quote! { <'a, B: Into<reqwest::Body> > }
625 } else {
626 quote! { <'a> }
627 };
628
629 let doc_comment = make_doc_comment(method);
630
631 let MethodSigBody {
632 success: success_type,
633 error: error_type,
634 body,
635 } = self.method_sig_body(method, quote! { Self }, quote! { self }, has_inner)?;
636
637 let method_impl = quote! {
638 #[doc = #doc_comment]
639 pub async fn #operation_id #bounds (
640 &'a self,
641 #(#params),*
642 ) -> Result<
643 ResponseValue<#success_type>,
644 Error<#error_type>,
645 > {
646 #body
647 }
648 };
649
650 let stream_impl = method.dropshot_paginated.as_ref().map(|page_data| {
651 self.uses_futures = true;
653
654 let stream_id = format_ident!("{}_stream", method.operation_id);
655
656 let stream_params = method
659 .params
660 .iter()
661 .zip(params)
662 .filter_map(|(param, stream)| {
663 if param.name.as_str() == "page_token" {
664 None
665 } else {
666 Some(stream)
667 }
668 });
669
670 let first_params = method.params.iter().map(|param| {
673 if param.api_name.as_str() == "page_token" {
674 quote! { None }
676 } else {
677 format_ident!("{}", param.name).to_token_stream()
679 }
680 });
681
682 let step_params = method.params.iter().map(|param| {
687 if param.api_name.as_str() == "page_token" {
688 quote! { state.as_deref() }
689 } else if param.api_name.as_str() != "limit"
690 && matches!(param.kind, OperationParameterKind::Query(_))
691 {
692 quote! { None }
696 } else {
697 format_ident!("{}", param.name).to_token_stream()
702 }
703 });
704
705 let item = self.type_space.get_type(&page_data.item).unwrap();
709 let item_type = item.ident();
710
711 let doc_comment = make_stream_doc_comment(method);
712
713 quote! {
714 #[doc = #doc_comment]
715 pub fn #stream_id #bounds (
716 &'a self,
717 #(#stream_params),*
718 ) -> impl futures::Stream<Item = Result<
719 #item_type,
720 Error<#error_type>,
721 >> + Unpin + 'a {
722 use futures::StreamExt;
723 use futures::TryFutureExt;
724 use futures::TryStreamExt;
725
726 self.#operation_id( #(#first_params,)* )
729 .map_ok(move |page| {
730 let page = page.into_inner();
731
732 let first =
734 futures::stream::iter(page.items).map(Ok);
735
736 let rest = futures::stream::try_unfold(
740 page.next_page,
741 move |state| async move {
742 if state.is_none() {
743 Ok(None)
746 } else {
747 self.#operation_id(
753 #(#step_params,)*
754 )
755 .map_ok(|page| {
756 let page = page.into_inner();
757 Some((
758 futures::stream::iter(
759 page.items
760 ).map(Ok),
761 page.next_page,
762 ))
763 })
764 .await
765 }
766 },
767 )
768 .try_flatten();
769
770 first.chain(rest)
771 })
772 .try_flatten_stream()
773 .boxed()
774 }
775 }
776 });
777
778 let all = quote! {
779 #method_impl
780 #stream_impl
781 };
782
783 Ok(all)
784 }
785
786 fn method_sig_body(
790 &self,
791 method: &OperationMethod,
792 client_type: TokenStream,
793 client_value: TokenStream,
794 has_inner: bool,
795 ) -> Result<MethodSigBody> {
796 let param_names = method
797 .params
798 .iter()
799 .map(|param| format_ident!("{}", param.name))
800 .collect::<Vec<_>>();
801
802 let url_ident = unique_ident_from("url", ¶m_names);
804 let request_ident = unique_ident_from("request", ¶m_names);
805 let response_ident = unique_ident_from("response", ¶m_names);
806 let result_ident = unique_ident_from("result", ¶m_names);
807
808 let query_params = method
810 .params
811 .iter()
812 .filter_map(|param| match ¶m.kind {
813 OperationParameterKind::Query(_) => {
814 let qn = ¶m.api_name;
815 let qn_ident = format_ident!("{}", ¶m.name);
816 Some(quote! {
817 &progenitor_middleware_client::QueryParam::new(#qn, &#qn_ident)
818 })
819 }
820 _ => None,
821 })
822 .collect::<Vec<_>>();
823
824 let headers = method
825 .params
826 .iter()
827 .filter_map(|param| match ¶m.kind {
828 OperationParameterKind::Header(required) => {
829 let hn = ¶m.api_name;
830 let hn_ident = format_ident!("{}", ¶m.name);
831 let res = if *required {
832 quote! {
833 header_map.append(
834 #hn,
835 #hn_ident.to_string().try_into()?
836 );
837 }
838 } else {
839 quote! {
840 if let Some(value) = #hn_ident {
841 header_map.append(
842 #hn,
843 value.to_string().try_into()?
844 );
845 }
846 }
847 };
848 Some(res)
849 }
850 _ => None,
851 })
852 .collect::<Vec<_>>();
853
854 let headers_size = headers.len() + 1;
855 let headers_build = quote! {
856 let mut header_map = ::reqwest::header::HeaderMap::with_capacity(#headers_size);
857 header_map.append(
858 ::reqwest::header::HeaderName::from_static("api-version"),
859 ::reqwest::header::HeaderValue::from_static(#client_type::api_version()),
860 );
861
862 #(#headers)*
863 };
864
865 let headers_use = quote! {
866 .headers(header_map)
867 };
868
869 let websock_hdrs = if method.dropshot_websocket {
870 quote! {
871 .header(::reqwest::header::CONNECTION, "Upgrade")
872 .header(::reqwest::header::UPGRADE, "websocket")
873 .header(::reqwest::header::SEC_WEBSOCKET_VERSION, "13")
874 .header(
875 ::reqwest::header::SEC_WEBSOCKET_KEY,
876 ::base64::Engine::encode(
877 &::base64::engine::general_purpose::STANDARD,
878 ::rand::random::<[u8; 16]>(),
879 )
880 )
881 }
882 } else {
883 quote! {}
884 };
885
886 let url_renames = method
889 .params
890 .iter()
891 .filter_map(|param| match ¶m.kind {
892 OperationParameterKind::Path => Some((¶m.api_name, ¶m.name)),
893 _ => None,
894 })
895 .collect();
896
897 let url_path = method.path.compile(url_renames, client_value.clone());
898 let url_path = quote! {
899 let #url_ident = #url_path;
900 };
901
902 let body_func = method.params.iter().filter_map(|param| {
904 match (¶m.kind, ¶m.typ) {
905 (
906 OperationParameterKind::Body(BodyContentType::OctetStream),
907 OperationParameterType::RawBody,
908 ) => Some(quote! {
909 .header(
912 ::reqwest::header::CONTENT_TYPE,
913 ::reqwest::header::HeaderValue::from_static("application/octet-stream"),
914 )
915 .body(body)
916 }),
917 (
918 OperationParameterKind::Body(BodyContentType::Text(mime_type)),
919 OperationParameterType::RawBody,
920 ) => Some(quote! {
921 .header(
924 ::reqwest::header::CONTENT_TYPE,
925 ::reqwest::header::HeaderValue::from_static(#mime_type),
926 )
927 .body(body)
928 }),
929 (
930 OperationParameterKind::Body(BodyContentType::Json),
931 OperationParameterType::Type(_),
932 ) => Some(quote! {
933 .json(&body)
935 }),
936 (
937 OperationParameterKind::Body(BodyContentType::FormUrlencoded),
938 OperationParameterType::Type(_),
939 ) => Some(quote! {
940 .form_urlencoded(&body)?
943 }),
944 (OperationParameterKind::Body(_), _) => {
945 unreachable!("invalid body kind/type combination")
946 }
947 _ => None,
948 }
949 });
950 assert!(body_func.clone().count() <= 1);
952
953 let (success_response_items, response_type) =
954 self.extract_responses(method, OperationResponseStatus::is_success_or_default);
955
956 let (error_response_items, error_response_type) =
958 self.extract_responses(method, OperationResponseStatus::is_error_or_default);
959
960 let error_type_annotation = match &error_response_type {
962 ErrorResponseType::Single(kind) => {
963 let error_tokens = kind.clone().into_tokens(&self.type_space);
964 quote! { #error_tokens }
965 }
966 ErrorResponseType::Multiple { enum_name, .. } => {
967 let enum_ident = format_ident!("{}", enum_name);
968 quote! { types::#enum_ident }
969 }
970 };
971
972 let success_response_matches = success_response_items.iter().map(|response| {
973 let pat = match &response.status_code {
974 OperationResponseStatus::Code(code) => quote! { #code },
975 OperationResponseStatus::Range(_) | OperationResponseStatus::Default => {
976 quote! { 200 ..= 299 }
977 }
978 };
979
980 let decode = match &response.typ {
981 OperationResponseKind::Type(_) => {
982 quote! {
983 ResponseValue::from_response::<#error_type_annotation>(#response_ident).await
984 }
985 }
986 OperationResponseKind::None => {
987 quote! {
988 Ok(ResponseValue::empty(#response_ident))
989 }
990 }
991 OperationResponseKind::Raw => {
992 quote! {
993 Ok(ResponseValue::stream(#response_ident))
994 }
995 }
996 OperationResponseKind::Upgrade => {
997 quote! {
998 ResponseValue::upgrade::<#error_type_annotation>(#response_ident).await
999 }
1000 }
1001 };
1002
1003 quote! { #pat => { #decode } }
1004 });
1005
1006 let generate_error_match_arm = |response: &OperationResponse,
1008 enum_info: Option<(&str, &str)>|
1009 -> TokenStream {
1010 let pat = match &response.status_code {
1011 OperationResponseStatus::Code(code) => {
1012 quote! { #code }
1013 }
1014 OperationResponseStatus::Range(r) => {
1015 let min = r * 100;
1016 let max = min + 99;
1017 quote! { #min ..= #max }
1018 }
1019 OperationResponseStatus::Default => {
1020 quote! { _ }
1021 }
1022 };
1023
1024 let decode = match &response.typ {
1025 OperationResponseKind::Type(type_id) => {
1026 if let Some((enum_name, variant_name)) = enum_info {
1027 let enum_ident = format_ident!("{}", enum_name);
1028 let variant_ident = format_ident!("{}", variant_name);
1029 let inner_type_tokens = response.typ.clone().into_tokens(&self.type_space);
1031
1032 quote! {
1033 {
1034 let inner_value: ResponseValue<#inner_type_tokens> = ResponseValue::from_response::<types::#enum_ident>(#response_ident).await?;
1035 let status = inner_value.status();
1036 let headers = inner_value.headers().clone();
1037 let inner = inner_value.into_inner();
1038 Err(Error::ErrorResponse(
1039 ResponseValue::new(
1040 types::#enum_ident::#variant_ident(inner),
1041 status,
1042 headers
1043 )
1044 ))
1045 }
1046 }
1047 } else {
1048 quote! {
1049 Err(Error::ErrorResponse(
1050 ResponseValue::from_response(#response_ident).await?
1051 ))
1052 }
1053 }
1054 }
1055 OperationResponseKind::None => {
1056 if let Some((enum_name, variant_name)) = enum_info {
1057 let enum_ident = format_ident!("{}", enum_name);
1058 let variant_ident = format_ident!("{}", variant_name);
1059 quote! {
1060 {
1061 let inner_value = ResponseValue::empty(#response_ident);
1062 let status = inner_value.status();
1063 let headers = inner_value.headers().clone();
1064 Err(Error::ErrorResponse(
1065 ResponseValue::new(
1066 types::#enum_ident::#variant_ident,
1067 status,
1068 headers
1069 )
1070 ))
1071 }
1072 }
1073 } else {
1074 quote! {
1075 Err(Error::ErrorResponse(
1076 ResponseValue::empty(#response_ident)
1077 ))
1078 }
1079 }
1080 }
1081 OperationResponseKind::Raw => {
1082 if let Some((enum_name, variant_name)) = enum_info {
1083 let enum_ident = format_ident!("{}", enum_name);
1084 let variant_ident = format_ident!("{}", variant_name);
1085 quote! {
1086 {
1087 let inner_value = ResponseValue::stream(#response_ident);
1088 let status = inner_value.status();
1089 let headers = inner_value.headers().clone();
1090 let inner = inner_value.into_inner_stream();
1091 Err(Error::ErrorResponse(
1092 ResponseValue::new(
1093 types::#enum_ident::#variant_ident(inner),
1094 status,
1095 headers
1096 )
1097 ))
1098 }
1099 }
1100 } else {
1101 quote! {
1102 Err(Error::ErrorResponse(
1103 ResponseValue::stream(#response_ident)
1104 ))
1105 }
1106 }
1107 }
1108 OperationResponseKind::Upgrade => {
1109 if response.status_code == OperationResponseStatus::Default {
1110 return quote! {}; } else {
1112 todo!(
1113 "non-default error response handling for \
1114 upgrade requests is not yet implemented"
1115 );
1116 }
1117 }
1118 };
1119
1120 quote! { #pat => { #decode } }
1121 };
1122
1123 let error_response_matches = match &error_response_type {
1124 ErrorResponseType::Single(_) => {
1125 error_response_items
1127 .iter()
1128 .map(|response| generate_error_match_arm(response, None))
1129 .collect::<Vec<_>>()
1130 }
1131 ErrorResponseType::Multiple {
1132 enum_name,
1133 variants,
1134 } => {
1135 error_response_items
1137 .iter()
1138 .map(|response| {
1139 let variant = variants
1141 .iter()
1142 .find(|v| v.status_codes.contains(&response.status_code))
1143 .expect("Response must map to a variant");
1144
1145 generate_error_match_arm(
1146 response,
1147 Some((enum_name.as_str(), variant.variant_name.as_str())),
1148 )
1149 })
1150 .collect::<Vec<_>>()
1151 }
1152 };
1153
1154 let accept_header = {
1155 let has_error_type = match &error_response_type {
1156 ErrorResponseType::Single(kind) => matches!(kind, OperationResponseKind::Type(_)),
1157 ErrorResponseType::Multiple { .. } => true, };
1159
1160 let has_success_type = match &response_type {
1161 ErrorResponseType::Single(kind) => matches!(kind, OperationResponseKind::Type(_)),
1162 ErrorResponseType::Multiple { .. } => true,
1163 };
1164 let has_success_none = match &response_type {
1165 ErrorResponseType::Single(kind) => matches!(kind, OperationResponseKind::None),
1166 ErrorResponseType::Multiple { .. } => false,
1167 };
1168
1169 has_success_type || (has_success_none && has_error_type)
1170 }
1171 .then(|| {
1172 quote! {
1173 .header(
1174 ::reqwest::header::ACCEPT,
1175 ::reqwest::header::HeaderValue::from_static(
1176 "application/json",
1177 ),
1178 )
1179 }
1180 });
1181
1182 let default_response = match method.responses.iter().last() {
1189 Some(response) if response.status_code.is_default() => quote! {},
1190 _ => {
1191 quote! { _ => Err(Error::UnexpectedResponse(#response_ident)), }
1192 }
1193 };
1194
1195 let inner = match has_inner {
1196 true => quote! { &#client_value.inner, },
1197 false => quote! {},
1198 };
1199 let pre_hook = self.settings.pre_hook.as_ref().map(|hook| {
1200 quote! {
1201 (#hook)(#inner &#request_ident);
1202 }
1203 });
1204 let pre_hook_async = self.settings.pre_hook_async.as_ref().map(|hook| {
1205 quote! {
1206 match (#hook)(#inner &mut #request_ident).await {
1207 Ok(_) => (),
1208 Err(e) => return Err(Error::Custom(e.to_string())),
1209 }
1210 }
1211 });
1212 let post_hook = self.settings.post_hook.as_ref().map(|hook| {
1213 quote! {
1214 (#hook)(#inner &#result_ident);
1215 }
1216 });
1217 let post_hook_async = self.settings.post_hook_async.as_ref().map(|hook| {
1218 quote! {
1219 match (#hook)(#inner &#result_ident).await {
1220 Ok(_) => (),
1221 Err(e) => return Err(Error::Custom(e.to_string())),
1222 }
1223 }
1224 });
1225
1226 let operation_id = &method.operation_id;
1227 let method_func = format_ident!("{}", method.method.as_str());
1228
1229 let body_impl = quote! {
1230 #url_path
1231
1232 #headers_build
1233
1234 #[allow(unused_mut)]
1235 let mut #request_ident = #client_value.client
1236 . #method_func (#url_ident)
1237 #accept_header
1238 #(#body_func)*
1239 #( .query(#query_params) )*
1240 #headers_use
1241 #websock_hdrs
1242 .build()?;
1243
1244 let info = OperationInfo {
1245 operation_id: #operation_id,
1246 };
1247
1248 #pre_hook
1249 #pre_hook_async
1250 #client_value
1251 .pre(&mut #request_ident, &info)
1252 .await?;
1253
1254 let #result_ident = #client_value
1255 .exec(#request_ident, &info)
1256 .await;
1257
1258 #client_value
1259 .post(&#result_ident, &info)
1260 .await?;
1261 #post_hook_async
1262 #post_hook
1263
1264 let #response_ident = #result_ident?;
1265
1266 match #response_ident.status().as_u16() {
1267 #(#success_response_matches)*
1284
1285 #(#error_response_matches)*
1293
1294 #default_response
1299 }
1300 };
1301
1302 let error_type_tokens = match &error_response_type {
1303 ErrorResponseType::Single(kind) => kind.clone().into_tokens(&self.type_space),
1304 ErrorResponseType::Multiple { enum_name, .. } => {
1305 let enum_ident = format_ident!("{}", enum_name);
1306 quote! { types::#enum_ident }
1307 }
1308 };
1309
1310 let success_type_tokens = match &response_type {
1311 ErrorResponseType::Single(kind) => kind.clone().into_tokens(&self.type_space),
1312 ErrorResponseType::Multiple { enum_name, .. } => {
1313 let enum_ident = format_ident!("{}", enum_name);
1314 quote! { types::#enum_ident }
1315 }
1316 };
1317
1318 Ok(MethodSigBody {
1319 success: success_type_tokens,
1320 error: error_type_tokens,
1321 body: body_impl,
1322 })
1323 }
1324
1325 pub fn extract_responses<'a>(
1330 &self,
1331 method: &'a OperationMethod,
1332 filter: fn(&OperationResponseStatus) -> bool,
1333 ) -> (Vec<&'a OperationResponse>, ErrorResponseType) {
1334 let mut response_items = method
1335 .responses
1336 .iter()
1337 .filter(|response| filter(&response.status_code))
1338 .collect::<Vec<_>>();
1339 response_items.sort();
1340
1341 let len = response_items.len();
1345 if len >= 2 {
1346 if let (
1347 OperationResponse {
1348 status_code: OperationResponseStatus::Range(2),
1349 ..
1350 },
1351 OperationResponse {
1352 status_code: OperationResponseStatus::Default,
1353 ..
1354 },
1355 ) = (&response_items[len - 2], &response_items[len - 1])
1356 {
1357 response_items.pop();
1358 }
1359 }
1360
1361 let response_types = response_items
1362 .iter()
1363 .map(|response| response.typ.clone())
1364 .collect::<BTreeSet<_>>();
1365
1366 if response_types.len() <= 1 {
1368 let response_type = response_types
1369 .into_iter()
1370 .next()
1371 .unwrap_or(OperationResponseKind::None);
1372 return (response_items, ErrorResponseType::Single(response_type));
1373 }
1374
1375 let enum_name = format!("{}Error", sanitize(&method.operation_id, Case::Pascal));
1377 let variants = self.create_error_variants(&response_items);
1378
1379 (
1380 response_items,
1381 ErrorResponseType::Multiple {
1382 enum_name,
1383 variants,
1384 },
1385 )
1386 }
1387
1388 pub fn generate_error_enum(&self, enum_name: &str, variants: &[ErrorVariant]) -> TokenStream {
1390 let enum_ident = format_ident!("{}", enum_name);
1391
1392 let variant_defs = variants.iter().map(|v| {
1393 let variant_ident = format_ident!("{}", v.variant_name);
1394
1395 match &v.typ {
1396 OperationResponseKind::Type(_) => {
1397 let typ_tokens = v.typ.clone().into_tokens(&self.type_space);
1399
1400 let typ_string = typ_tokens.to_string();
1402 let typ_without_prefix =
1403 typ_string.strip_prefix("types :: ").unwrap_or(&typ_string);
1404
1405 let typ_tokens: TokenStream = typ_without_prefix.parse().expect(&format!(
1407 "Failed to parse type tokens: {}",
1408 typ_without_prefix
1409 ));
1410
1411 quote! { #variant_ident(#typ_tokens) }
1412 }
1413 OperationResponseKind::None => {
1414 quote! { #variant_ident }
1415 }
1416 OperationResponseKind::Raw => {
1417 quote! { #variant_ident(ByteStream) }
1419 }
1420 OperationResponseKind::Upgrade => {
1421 quote! { #variant_ident(reqwest::Upgraded) }
1422 }
1423 }
1424 });
1425
1426 quote! {
1427 #[derive(Debug)]
1428 pub enum #enum_ident {
1429 #(#variant_defs,)*
1430 }
1431 }
1432 }
1433
1434 fn create_error_variants(&self, responses: &[&OperationResponse]) -> Vec<ErrorVariant> {
1436 let mut variants = Vec::new();
1437
1438 for response in responses {
1440 let variant_name = match &response.status_code {
1441 OperationResponseStatus::Code(code) => format!("Status{}", code),
1442 OperationResponseStatus::Range(r) => format!("Status{}xx", r),
1443 OperationResponseStatus::Default => "DefaultResponse".to_string(),
1444 };
1445
1446 variants.push(ErrorVariant {
1447 variant_name,
1448 typ: response.typ.clone(),
1449 status_codes: vec![response.status_code.clone()],
1450 });
1451 }
1452
1453 variants
1454 }
1455
1456 fn dropshot_pagination_data(
1459 &self,
1460 operation: &openapiv3::Operation,
1461 parameters: &[OperationParameter],
1462 responses: &[OperationResponse],
1463 ) -> Option<DropshotPagination> {
1464 let value = operation.extensions.get("x-dropshot-pagination")?;
1465
1466 if parameters
1468 .iter()
1469 .filter(|param| {
1470 matches!(
1471 (param.api_name.as_str(), ¶m.kind),
1472 ("page_token", OperationParameterKind::Query(false))
1473 | ("limit", OperationParameterKind::Query(false))
1474 )
1475 })
1476 .count()
1477 != 2
1478 {
1479 return None;
1480 }
1481
1482 if !parameters.iter().all(|param| match ¶m.kind {
1485 OperationParameterKind::Query(required) => !required,
1486 _ => true,
1487 }) {
1488 return None;
1489 }
1490
1491 if parameters
1496 .iter()
1497 .any(|param| param.typ == OperationParameterType::RawBody)
1498 {
1499 return None;
1500 }
1501
1502 let mut success_response_items =
1504 responses
1505 .iter()
1506 .filter_map(|response| match (&response.status_code, &response.typ) {
1507 (
1508 OperationResponseStatus::Code(200..=299)
1509 | OperationResponseStatus::Range(2),
1510 OperationResponseKind::Type(type_id),
1511 ) => Some(type_id),
1512 _ => None,
1513 });
1514
1515 let success_response = match (success_response_items.next(), success_response_items.next())
1516 {
1517 (None, _) | (_, Some(_)) => return None,
1518 (Some(success), None) => success,
1519 };
1520
1521 let typ = self.type_space.get_type(success_response).ok()?;
1522 let details = match typ.details() {
1523 typify::TypeDetails::Struct(details) => details,
1524 _ => return None,
1525 };
1526
1527 let properties = details.properties().collect::<BTreeMap<_, _>>();
1528
1529 if properties.len() != 2 {
1531 return None;
1532 }
1533
1534 if let typify::TypeDetails::Option(ref opt_id) = self
1536 .type_space
1537 .get_type(properties.get("next_page")?)
1538 .ok()?
1539 .details()
1540 {
1541 if !matches!(
1542 self.type_space.get_type(opt_id).ok()?.details(),
1543 typify::TypeDetails::String
1544 ) {
1545 return None;
1546 }
1547 } else {
1548 return None;
1549 }
1550
1551 match self
1552 .type_space
1553 .get_type(properties.get("items")?)
1554 .ok()?
1555 .details()
1556 {
1557 typify::TypeDetails::Vec(item) => {
1558 #[derive(serde::Deserialize, Default)]
1559 struct DropshotPaginationFormat {
1560 required: Vec<String>,
1561 }
1562 let first_page_params =
1563 serde_json::from_value::<DropshotPaginationFormat>(value.clone())
1564 .unwrap_or_default()
1565 .required;
1566 Some(DropshotPagination {
1567 item,
1568 first_page_params,
1569 })
1570 }
1571 _ => None,
1572 }
1573 }
1574
1575 pub(crate) fn builder_struct(
1651 &mut self,
1652 method: &OperationMethod,
1653 tag_style: TagStyle,
1654 has_inner: bool,
1655 ) -> Result<TokenStream> {
1656 let struct_name = sanitize(&method.operation_id, Case::Pascal);
1657 let struct_ident = format_ident!("{}", struct_name);
1658
1659 let param_names = method
1661 .params
1662 .iter()
1663 .map(|param| format_ident!("{}", param.name))
1664 .collect::<Vec<_>>();
1665
1666 let client_ident = unique_ident_from("client", ¶m_names);
1667
1668 let mut cloneable = true;
1669
1670 let param_types = method
1672 .params
1673 .iter()
1674 .map(|param| match ¶m.typ {
1675 OperationParameterType::Type(type_id) => {
1676 let ty = self.type_space.get_type(type_id)?;
1677
1678 if let (OperationParameterKind::Body(_), Some(builder_name)) =
1681 (¶m.kind, ty.builder())
1682 {
1683 Ok(quote! { Result<#builder_name, String> })
1684 } else if param.kind.is_required() {
1685 let t = ty.ident();
1686 Ok(quote! { Result<#t, String> })
1687 } else {
1688 let t = ty.ident();
1689 Ok(quote! { Result<Option<#t>, String> })
1690 }
1691 }
1692
1693 OperationParameterType::RawBody => {
1694 cloneable = false;
1695 Ok(quote! { Result<reqwest::Body, String> })
1696 }
1697 })
1698 .collect::<Result<Vec<_>>>()?;
1699
1700 let param_values = method
1705 .params
1706 .iter()
1707 .map(|param| match ¶m.typ {
1708 OperationParameterType::Type(type_id) => {
1709 let ty = self.type_space.get_type(type_id)?;
1710
1711 if let (OperationParameterKind::Body(_), Some(_)) = (¶m.kind, ty.builder())
1714 {
1715 Ok(quote! { Ok(::std::default::Default::default()) })
1716 } else if param.kind.is_required() {
1717 let err_msg = format!("{} was not initialized", param.name);
1718 Ok(quote! { Err(#err_msg.to_string()) })
1719 } else {
1720 Ok(quote! { Ok(None) })
1721 }
1722 }
1723
1724 OperationParameterType::RawBody => {
1725 let err_msg = format!("{} was not initialized", param.name);
1726 Ok(quote! { Err(#err_msg.to_string()) })
1727 }
1728 })
1729 .collect::<Result<Vec<_>>>()?;
1730
1731 let param_finalize = method
1735 .params
1736 .iter()
1737 .map(|param| match ¶m.typ {
1738 OperationParameterType::Type(type_id) => {
1739 let ty = self.type_space.get_type(type_id)?;
1740 if ty.builder().is_some() {
1741 let type_name = ty.ident();
1742 Ok(quote! {
1743 .and_then(|v| #type_name::try_from(v)
1744 .map_err(|e| e.to_string()))
1745 })
1746 } else {
1747 Ok(quote! {})
1748 }
1749 }
1750 OperationParameterType::RawBody => Ok(quote! {}),
1751 })
1752 .collect::<Result<Vec<_>>>()?;
1753
1754 let param_impls = method
1757 .params
1758 .iter()
1759 .map(|param| {
1760 let param_name = format_ident!("{}", param.name);
1761 match ¶m.typ {
1762 OperationParameterType::Type(type_id) => {
1763 let ty = self.type_space.get_type(type_id)?;
1764 match (ty.builder(), param.kind.is_optional()) {
1765 (Some(_), true) => {
1768 unreachable!()
1769 }
1770 (None, true) => {
1771 let typ = ty.ident();
1772 let err_msg = format!(
1773 "conversion to `{}` for {} failed",
1774 ty.name(),
1775 param.name,
1776 );
1777 Ok(quote! {
1778 pub fn #param_name<V>(
1779 mut self,
1780 value: V,
1781 ) -> Self
1782 where V: std::convert::TryInto<#typ>,
1783 {
1784 self.#param_name = value.try_into()
1785 .map(Some)
1786 .map_err(|_| #err_msg.to_string());
1787 self
1788 }
1789 })
1790 }
1791 (None, false) => {
1792 let typ = ty.ident();
1793 let err_msg = format!(
1794 "conversion to `{}` for {} failed",
1795 ty.name(),
1796 param.name,
1797 );
1798 Ok(quote! {
1799 pub fn #param_name<V>(
1800 mut self,
1801 value: V,
1802 ) -> Self
1803 where V: std::convert::TryInto<#typ>,
1804 {
1805 self.#param_name = value.try_into()
1806 .map_err(|_| #err_msg.to_string());
1807 self
1808 }
1809 })
1810 }
1811
1812 (Some(builder_name), false) => {
1818 assert_eq!(param.name, "body");
1819 let typ = ty.ident();
1820 let err_msg = format!(
1821 "conversion to `{}` for {} failed: {{}}",
1822 ty.name(),
1823 param.name,
1824 );
1825 Ok(quote! {
1826 pub fn body<V>(mut self, value: V) -> Self
1827 where
1828 V: std::convert::TryInto<#typ>,
1829 <V as std::convert::TryInto<#typ>>::Error:
1830 std::fmt::Display,
1831 {
1832 self.body = value.try_into()
1833 .map(From::from)
1834 .map_err(|s| format!(#err_msg, s));
1835 self
1836 }
1837
1838 pub fn body_map<F>(mut self, f: F) -> Self
1839 where
1840 F: std::ops::FnOnce(#builder_name)
1841 -> #builder_name,
1842 {
1843 self.body = self.body.map(f);
1844 self
1845 }
1846 })
1847 }
1848 }
1849 }
1850
1851 OperationParameterType::RawBody => match param.kind {
1852 OperationParameterKind::Body(BodyContentType::OctetStream) => {
1853 let err_msg =
1854 format!("conversion to `reqwest::Body` for {} failed", param.name,);
1855
1856 Ok(quote! {
1857 pub fn #param_name<B>(mut self, value: B) -> Self
1858 where B: std::convert::TryInto<reqwest::Body>
1859 {
1860 self.#param_name = value.try_into()
1861 .map_err(|_| #err_msg.to_string());
1862 self
1863 }
1864 })
1865 }
1866 OperationParameterKind::Body(BodyContentType::Text(_)) => {
1867 let err_msg =
1868 format!("conversion to `String` for {} failed", param.name,);
1869
1870 Ok(quote! {
1871 pub fn #param_name<V>(mut self, value: V) -> Self
1872 where V: std::convert::TryInto<String>
1873 {
1874 self.#param_name = value
1875 .try_into()
1876 .map_err(|_| #err_msg.to_string())
1877 .map(|v| v.into());
1878 self
1879 }
1880 })
1881 }
1882 _ => unreachable!(),
1883 },
1884 }
1885 })
1886 .collect::<Result<Vec<_>>>()?;
1887
1888 let MethodSigBody {
1889 success,
1890 error,
1891 body,
1892 } = self.method_sig_body(
1893 method,
1894 quote! { super::Client },
1895 quote! { #client_ident },
1896 has_inner,
1897 )?;
1898
1899 let send_doc = format!(
1900 "Sends a `{}` request to `{}`",
1901 method.method.as_str().to_ascii_uppercase(),
1902 method.path.to_string(),
1903 );
1904 let send_impl = quote! {
1905 #[doc = #send_doc]
1906 pub async fn send(self) -> Result<
1907 ResponseValue<#success>,
1908 Error<#error>,
1909 > {
1910 let Self {
1912 #client_ident,
1913 #( #param_names, )*
1914 } = self;
1915
1916 #(
1923 let #param_names =
1924 #param_names
1925 #param_finalize
1926 .map_err(Error::InvalidRequest)?;
1927 )*
1928
1929 #body
1931 }
1932 };
1933
1934 let stream_impl = method.dropshot_paginated.as_ref().map(|page_data| {
1935 self.uses_futures = true;
1937
1938 let step_params = method.params.iter().filter_map(|param| {
1939 if param.api_name.as_str() != "limit"
1940 && matches!(param.kind, OperationParameterKind::Query(_))
1941 {
1942 let name = format_ident!("{}", param.name);
1946 Some(quote! {
1947 #name: Ok(None)
1948 })
1949 } else {
1950 None
1951 }
1952 });
1953
1954 let item = self.type_space.get_type(&page_data.item).unwrap();
1958 let item_type = item.ident();
1959
1960 let stream_doc = format!(
1961 "Streams `{}` requests to `{}`",
1962 method.method.as_str().to_ascii_uppercase(),
1963 method.path.to_string(),
1964 );
1965
1966 quote! {
1967 #[doc = #stream_doc]
1968 pub fn stream(self) -> impl futures::Stream<Item = Result<
1969 #item_type,
1970 Error<#error>,
1971 >> + Unpin + 'a {
1972 use ::futures::StreamExt;
1973 use ::futures::TryFutureExt;
1974 use ::futures::TryStreamExt;
1975
1976 let next = Self {
1980 #( #step_params, )*
1981 ..self.clone()
1982 };
1983
1984 self.send()
1985 .map_ok(move |page| {
1986 let page = page.into_inner();
1987
1988 let first =
1990 futures::stream::iter(page.items).map(Ok);
1991
1992 let rest = futures::stream::try_unfold(
1997 (page.next_page, next),
1998 |(next_page, next)| async {
1999 if next_page.is_none() {
2000 Ok(None)
2003 } else {
2004 Self {
2008 page_token: Ok(next_page),
2009 ..next.clone()
2010 }
2011 .send()
2012 .map_ok(|page| {
2013 let page = page.into_inner();
2014 Some((
2015 futures::stream::iter(
2016 page.items
2017 ).map(Ok),
2018 (page.next_page, next),
2019 ))
2020 })
2021 .await
2022 }
2023 },
2024 )
2025 .try_flatten();
2026
2027 first.chain(rest)
2028 })
2029 .try_flatten_stream()
2030 .boxed()
2031 }
2032 }
2033 });
2034
2035 let mut derives = vec![quote! { Debug }];
2036 if cloneable {
2037 derives.push(quote! { Clone });
2038 }
2039
2040 let derive = quote! {
2041 #[derive( #( #derives ),* )]
2042 };
2043
2044 let struct_doc = match (tag_style, method.tags.len(), method.tags.first()) {
2050 (TagStyle::Merged, _, _) | (TagStyle::Separate, 0, _) => {
2051 let ty = format!("Client::{}", method.operation_id);
2052 format!("Builder for [`{}`]\n\n[`{}`]: super::{}", ty, ty, ty,)
2053 }
2054 (TagStyle::Separate, 1, Some(tag)) => {
2055 let ty = format!(
2056 "Client{}Ext::{}",
2057 sanitize(tag, Case::Pascal),
2058 method.operation_id
2059 );
2060 format!("Builder for [`{}`]\n\n[`{}`]: super::{}", ty, ty, ty,)
2061 }
2062 (TagStyle::Separate, _, _) => {
2063 format!(
2064 "Builder for `{}` operation\n\nSee {}\n\n{}",
2065 method.operation_id,
2066 method
2067 .tags
2068 .iter()
2069 .map(|tag| {
2070 format!(
2071 "[`Client{}Ext::{}`]",
2072 sanitize(tag, Case::Pascal),
2073 method.operation_id,
2074 )
2075 })
2076 .collect::<Vec<_>>()
2077 .join(", "),
2078 method
2079 .tags
2080 .iter()
2081 .map(|tag| {
2082 let ty = format!(
2083 "Client{}Ext::{}",
2084 sanitize(tag, Case::Pascal),
2085 method.operation_id,
2086 );
2087 format!("[`{}`]: super::{}", ty, ty)
2088 })
2089 .collect::<Vec<_>>()
2090 .join("\n"),
2091 )
2092 }
2093 };
2094
2095 Ok(quote! {
2096 #[doc = #struct_doc]
2097 #derive
2098 pub struct #struct_ident<'a> {
2099 #client_ident: &'a super::Client,
2100 #( #param_names: #param_types, )*
2101 }
2102
2103 impl<'a> #struct_ident<'a> {
2104 pub fn new(client: &'a super::Client) -> Self {
2105 Self {
2106 #client_ident: client,
2107 #( #param_names: #param_values, )*
2108 }
2109 }
2110
2111 #( #param_impls )*
2112 #send_impl
2113 #stream_impl
2114 }
2115 })
2116 }
2117
2118 fn builder_helper(&self, method: &OperationMethod) -> BuilderImpl {
2119 let operation_id = format_ident!("{}", method.operation_id);
2120 let struct_name = sanitize(&method.operation_id, Case::Pascal);
2121 let struct_ident = format_ident!("{}", struct_name);
2122
2123 let params = method
2124 .params
2125 .iter()
2126 .map(|param| format!("\n .{}({})", param.name, param.name))
2127 .collect::<Vec<_>>()
2128 .join("");
2129
2130 let eg = format!(
2131 "\
2132 let response = client.{}(){}
2133 .send()
2134 .await;",
2135 method.operation_id, params,
2136 );
2137
2138 let doc = format!("{}```ignore\n{}\n```", make_doc_comment(method), eg);
2143
2144 let sig = quote! {
2145 fn #operation_id(&self) -> builder:: #struct_ident <'_>
2146 };
2147
2148 let body = quote! {
2149 builder:: #struct_ident ::new(self)
2150 };
2151 BuilderImpl { doc, sig, body }
2152 }
2153
2154 pub(crate) fn builder_tags(
2175 &self,
2176 methods: &[OperationMethod],
2177 tag_info: &BTreeMap<&String, &openapiv3::Tag>,
2178 ) -> (TokenStream, TokenStream) {
2179 let mut base = Vec::new();
2180 let mut ext = BTreeMap::new();
2181
2182 methods.iter().for_each(|method| {
2183 let BuilderImpl { doc, sig, body } = self.builder_helper(method);
2184
2185 if method.tags.is_empty() {
2186 let impl_body = quote! {
2187 #[doc = #doc]
2188 pub #sig {
2189 #body
2190 }
2191 };
2192 base.push(impl_body);
2193 } else {
2194 let trait_sig = quote! {
2195 #[doc = #doc]
2196 #sig;
2197 };
2198
2199 let impl_body = quote! {
2200 #sig {
2201 #body
2202 }
2203 };
2204 method.tags.iter().for_each(|tag| {
2205 ext.entry(tag.clone())
2206 .or_insert_with(Vec::new)
2207 .push((trait_sig.clone(), impl_body.clone()));
2208 });
2209 }
2210 });
2211
2212 let base_impl = (!base.is_empty()).then(|| {
2213 quote! {
2214 impl Client {
2215 #(#base)*
2216 }
2217 }
2218 });
2219
2220 let (ext_impl, ext_use): (Vec<_>, Vec<_>) = ext
2221 .into_iter()
2222 .map(|(tag, trait_methods)| {
2223 let desc = tag_info
2224 .get(&tag)
2225 .and_then(|tag| tag.description.as_ref())
2226 .map(|d| quote! { #[doc = #d] });
2227 let tr = format_ident!("Client{}Ext", sanitize(&tag, Case::Pascal));
2228 let (trait_methods, trait_impls): (Vec<TokenStream>, Vec<TokenStream>) =
2229 trait_methods.into_iter().unzip();
2230 (
2231 quote! {
2232 #desc
2233 pub trait #tr {
2234 #(#trait_methods)*
2235 }
2236
2237 impl #tr for Client {
2238 #(#trait_impls)*
2239 }
2240 },
2241 tr,
2242 )
2243 })
2244 .unzip();
2245
2246 (
2247 quote! {
2248 #base_impl
2249
2250 #(#ext_impl)*
2251 },
2252 quote! {
2253 #(pub use super::#ext_use;)*
2254 },
2255 )
2256 }
2257
2258 pub(crate) fn builder_impl(&self, method: &OperationMethod) -> TokenStream {
2259 let BuilderImpl { doc, sig, body } = self.builder_helper(method);
2260
2261 let impl_body = quote! {
2262 #[doc = #doc]
2263 pub #sig {
2264 #body
2265 }
2266 };
2267
2268 impl_body
2269 }
2270
2271 fn get_body_param(
2272 &mut self,
2273 operation: &openapiv3::Operation,
2274 components: &Option<Components>,
2275 ) -> Result<Option<OperationParameter>> {
2276 let body = match &operation.request_body {
2277 Some(body) => body.item(components)?,
2278 None => return Ok(None),
2279 };
2280
2281 let (content_str, media_type) = match (body.content.first(), body.content.len()) {
2282 (None, _) => return Ok(None),
2283 (Some(first), 1) => first,
2284 (_, n) => todo!(
2285 "more media types than expected for {}: {}",
2286 operation.operation_id.as_ref().unwrap(),
2287 n,
2288 ),
2289 };
2290
2291 let schema = media_type.schema.as_ref().ok_or_else(|| {
2292 Error::UnexpectedFormat("No schema specified for request body".to_string())
2293 })?;
2294
2295 let content_type = BodyContentType::from_str(content_str)?;
2296
2297 let typ = match content_type {
2298 BodyContentType::OctetStream => {
2299 match schema.item(components)? {
2305 openapiv3::Schema {
2306 schema_data:
2307 openapiv3::SchemaData {
2308 nullable: false,
2309 discriminator: None,
2310 default: None,
2311 ..
2314 },
2315 schema_kind:
2316 openapiv3::SchemaKind::Type(openapiv3::Type::String(
2317 openapiv3::StringType {
2318 format:
2319 openapiv3::VariantOrUnknownOrEmpty::Item(
2320 openapiv3::StringFormat::Binary,
2321 ),
2322 pattern: None,
2323 enumeration,
2324 min_length: None,
2325 max_length: None,
2326 },
2327 )),
2328 } if enumeration.is_empty() => Ok(()),
2329 _ => Err(Error::UnexpectedFormat(format!(
2330 "invalid schema for application/octet-stream: {:?}",
2331 schema
2332 ))),
2333 }?;
2334 OperationParameterType::RawBody
2335 }
2336 BodyContentType::Text(_) => {
2337 match schema.item(components)? {
2342 openapiv3::Schema {
2343 schema_data:
2344 openapiv3::SchemaData {
2345 nullable: false,
2346 discriminator: None,
2347 default: None,
2348 ..
2351 },
2352 schema_kind:
2353 openapiv3::SchemaKind::Type(openapiv3::Type::String(
2354 openapiv3::StringType {
2355 format: openapiv3::VariantOrUnknownOrEmpty::Empty,
2356 pattern: None,
2357 enumeration,
2358 min_length: None,
2359 max_length: None,
2360 },
2361 )),
2362 } if enumeration.is_empty() => Ok(()),
2363 _ => Err(Error::UnexpectedFormat(format!(
2364 "invalid schema for {}: {:?}",
2365 content_type, schema
2366 ))),
2367 }?;
2368 OperationParameterType::RawBody
2369 }
2370 BodyContentType::Json | BodyContentType::FormUrlencoded => {
2371 if !media_type.encoding.is_empty() {
2375 todo!("media type encoding not empty: {:#?}", media_type);
2376 }
2377 let name = sanitize(
2378 &format!("{}-body", operation.operation_id.as_ref().unwrap(),),
2379 Case::Pascal,
2380 );
2381 let typ = self
2382 .type_space
2383 .add_type_with_name(&schema.to_schema(), Some(name))?;
2384 OperationParameterType::Type(typ)
2385 }
2386 };
2387
2388 Ok(Some(OperationParameter {
2389 name: "body".to_string(),
2390 api_name: "body".to_string(),
2391 description: body.description.clone(),
2392 typ,
2393 kind: OperationParameterKind::Body(content_type),
2394 }))
2395 }
2396}
2397
2398fn make_doc_comment(method: &OperationMethod) -> String {
2399 let mut buf = String::new();
2400
2401 if let Some(summary) = &method.summary {
2402 buf.push_str(summary.trim_end_matches(['.', ',']));
2403 buf.push_str("\n\n");
2404 }
2405 if let Some(description) = &method.description {
2406 buf.push_str(description);
2407 buf.push_str("\n\n");
2408 }
2409
2410 buf.push_str(&format!(
2411 "Sends a `{}` request to `{}`\n\n",
2412 method.method.as_str().to_ascii_uppercase(),
2413 method.path.to_string(),
2414 ));
2415
2416 if method
2417 .params
2418 .iter()
2419 .filter(|param| param.description.is_some())
2420 .count()
2421 > 0
2422 {
2423 buf.push_str("Arguments:\n");
2424 for param in &method.params {
2425 buf.push_str(&format!("- `{}`", param.name));
2426 if let Some(description) = ¶m.description {
2427 buf.push_str(": ");
2428 buf.push_str(description);
2429 }
2430 buf.push('\n');
2431 }
2432 }
2433
2434 buf
2435}
2436
2437fn make_stream_doc_comment(method: &OperationMethod) -> String {
2438 let mut buf = String::new();
2439
2440 if let Some(summary) = &method.summary {
2441 buf.push_str(summary.trim_end_matches(['.', ',']));
2442 buf.push_str(" as a Stream\n\n");
2443 }
2444 if let Some(description) = &method.description {
2445 buf.push_str(description);
2446 buf.push_str("\n\n");
2447 }
2448
2449 buf.push_str(&format!(
2450 "Sends repeated `{}` requests to `{}` until there are no more results.\n\n",
2451 method.method.as_str().to_ascii_uppercase(),
2452 method.path.to_string(),
2453 ));
2454
2455 if method
2456 .params
2457 .iter()
2458 .filter(|param| param.api_name.as_str() != "page_token")
2459 .filter(|param| param.description.is_some())
2460 .count()
2461 > 0
2462 {
2463 buf.push_str("Arguments:\n");
2464 for param in &method.params {
2465 if param.api_name.as_str() == "page_token" {
2466 continue;
2467 }
2468
2469 buf.push_str(&format!("- `{}`", param.name));
2470 if let Some(description) = ¶m.description {
2471 buf.push_str(": ");
2472 buf.push_str(description);
2473 }
2474 buf.push('\n');
2475 }
2476 }
2477
2478 buf
2479}
2480
2481fn sort_params(raw_params: &mut [OperationParameter], names: &[String]) {
2482 raw_params.sort_by(
2483 |OperationParameter {
2484 kind: a_kind,
2485 api_name: a_name,
2486 ..
2487 },
2488 OperationParameter {
2489 kind: b_kind,
2490 api_name: b_name,
2491 ..
2492 }| {
2493 match (a_kind, b_kind) {
2494 (OperationParameterKind::Path, OperationParameterKind::Path) => {
2496 let a_index = names
2497 .iter()
2498 .position(|x| x == a_name)
2499 .unwrap_or_else(|| panic!("{} missing from path", a_name));
2500 let b_index = names
2501 .iter()
2502 .position(|x| x == b_name)
2503 .unwrap_or_else(|| panic!("{} missing from path", b_name));
2504 a_index.cmp(&b_index)
2505 }
2506 (OperationParameterKind::Path, OperationParameterKind::Query(_)) => Ordering::Less,
2507 (OperationParameterKind::Path, OperationParameterKind::Body(_)) => Ordering::Less,
2508 (OperationParameterKind::Path, OperationParameterKind::Header(_)) => Ordering::Less,
2509
2510 (OperationParameterKind::Query(_), OperationParameterKind::Body(_)) => {
2512 Ordering::Less
2513 }
2514 (OperationParameterKind::Query(_), OperationParameterKind::Query(_)) => {
2515 a_name.cmp(b_name)
2516 }
2517 (OperationParameterKind::Query(_), OperationParameterKind::Path) => {
2518 Ordering::Greater
2519 }
2520 (OperationParameterKind::Query(_), OperationParameterKind::Header(_)) => {
2521 Ordering::Less
2522 }
2523
2524 (OperationParameterKind::Body(_), OperationParameterKind::Path) => {
2526 Ordering::Greater
2527 }
2528 (OperationParameterKind::Body(_), OperationParameterKind::Query(_)) => {
2529 Ordering::Greater
2530 }
2531 (OperationParameterKind::Body(_), OperationParameterKind::Header(_)) => {
2532 Ordering::Greater
2533 }
2534 (OperationParameterKind::Body(_), OperationParameterKind::Body(_)) => {
2535 panic!("should only be one body")
2536 }
2537
2538 (OperationParameterKind::Header(_), OperationParameterKind::Header(_)) => {
2540 a_name.cmp(b_name)
2541 }
2542 (OperationParameterKind::Header(_), _) => Ordering::Greater,
2543 }
2544 },
2545 );
2546}
2547
2548trait ParameterDataExt {
2549 fn schema(&self) -> Result<&openapiv3::ReferenceOr<openapiv3::Schema>>;
2550}
2551
2552impl ParameterDataExt for openapiv3::ParameterData {
2553 fn schema(&self) -> Result<&openapiv3::ReferenceOr<openapiv3::Schema>> {
2554 match &self.format {
2555 openapiv3::ParameterSchemaOrContent::Schema(s) => Ok(s),
2556 openapiv3::ParameterSchemaOrContent::Content(c) => Err(Error::UnexpectedFormat(
2557 format!("unexpected content {:#?}", c),
2558 )),
2559 }
2560 }
2561}