From 7979fe212a783ddc469cfee8c20eefbd7466eea1 Mon Sep 17 00:00:00 2001 From: Bernhard Schuster Date: Tue, 11 Apr 2023 18:29:35 +0200 Subject: [PATCH] refactor; somewhat cleaner approach --- progenitor-impl/src/lib.rs | 62 ++++++++++++++++++++++++----- progenitor-impl/src/method.rs | 73 ++++++++++++++--------------------- 2 files changed, 82 insertions(+), 53 deletions(-) diff --git a/progenitor-impl/src/lib.rs b/progenitor-impl/src/lib.rs index 331c3b1a..f2d729d2 100644 --- a/progenitor-impl/src/lib.rs +++ b/progenitor-impl/src/lib.rs @@ -7,6 +7,7 @@ use proc_macro2::TokenStream; use quote::quote; use serde::Deserialize; use thiserror::Error; +use typify::{TypeDetails, TypeId}; use typify::{TypeSpace, TypeSpaceSettings}; use crate::to_schema::ToSchema; @@ -40,6 +41,7 @@ pub type Result = std::result::Result; pub struct Generator { type_space: TypeSpace, + forms: HashSet, settings: GenerationSettings, uses_futures: bool, uses_websockets: bool, @@ -163,6 +165,7 @@ impl Default for Generator { type_space: TypeSpace::new( TypeSpaceSettings::default().with_type_mod("types"), ), + forms: Default::default(), settings: Default::default(), uses_futures: Default::default(), uses_websockets: Default::default(), @@ -204,6 +207,7 @@ impl Generator { Self { type_space: TypeSpace::new(&type_settings), settings: settings.clone(), + forms: Default::default(), uses_futures: false, uses_websockets: false, } @@ -262,6 +266,44 @@ impl Generator { let types = self.type_space.to_stream(); + let extra_impl = TokenStream::from_iter( + self.forms + .iter() + .map(|type_id| { + let typ = self.get_type_space().get_type(type_id).unwrap(); + let form_name = typ.name(); + let td = typ.details(); + let TypeDetails::Struct(tstru) = td else { unreachable!() }; + let properties = indexmap::IndexMap::<&'_ str, _>::from_iter( + tstru + .properties() + .filter_map(|(prop_name, prop_id)| { + self.get_type_space() + .get_type(&prop_id).ok() + .map(|prop_typ| (prop_name, prop_typ)) + }) + + ); + let properties = syn::punctuated::Punctuated::<_, syn::Token![,]>::from_iter( + properties + .into_iter() + .map(|(prop_name, prop_ty)| { + let ident = quote::format_ident!("{}", prop_name); + quote!{ (#prop_name, &self. #ident) } + })); + + let form_name = quote::format_ident!("{}",typ.name()); + + quote! { + impl #form_name { + pub fn as_form<'f>(&'f self) -> impl std::iter::Iterator { + [#properties] + .into_iter() + .filter_map(|(name, val)| val.as_ref().map(|val| (name, val.as_slice()))) + } + } + } + })); // Generate an implementation of a `Self::as_inner` method, if an inner // type is defined. let maybe_inner = self.settings.inner_type.as_ref().map(|inner| { @@ -290,20 +332,20 @@ impl Generator { }); let client_docstring = { - let mut s = format!("Client for {}", spec.info.title); + let mut doc = format!("Client for {}", spec.info.title); - if let Some(ss) = &spec.info.description { - s.push_str("\n\n"); - s.push_str(ss); + if let Some(desc) = &spec.info.description { + doc.push_str("\n\n"); + doc.push_str(desc); } - if let Some(ss) = &spec.info.terms_of_service { - s.push_str("\n\n"); - s.push_str(ss); + if let Some(tos) = &spec.info.terms_of_service { + doc.push_str("\n\n"); + doc.push_str(tos); } - s.push_str(&format!("\n\nVersion: {}", &spec.info.version)); + doc.push_str(&format!("\n\nVersion: {}", &spec.info.version)); - s + doc }; let version_str = &spec.info.version; @@ -325,6 +367,8 @@ impl Generator { use std::convert::TryFrom; #types + + #extra_impl } #[derive(Clone, Debug)] diff --git a/progenitor-impl/src/method.rs b/progenitor-impl/src/method.rs index 1ebbd9b7..15bc6d6e 100644 --- a/progenitor-impl/src/method.rs +++ b/progenitor-impl/src/method.rs @@ -10,7 +10,7 @@ use indexmap::{IndexMap, IndexSet}; use openapiv3::{Components, Parameter, ReferenceOr, Response, StatusCode}; use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens}; -use typify::{TypeId, TypeSpace}; +use typify::{TypeId, TypeSpace, TypeSpacePatch}; use crate::{ template::PathTemplate, @@ -105,7 +105,7 @@ pub struct OperationParameter { #[derive(Debug, Eq, PartialEq)] pub enum OperationParameterType { Type(TypeId), - Form(IndexSet), + Form(TypeId), RawBody, } @@ -585,7 +585,8 @@ impl Generator { .map(|param| { let name = format_ident!("{}", param.name); match ¶m.typ { - OperationParameterType::Type(type_id) => { + OperationParameterType::Type(type_id) + | OperationParameterType::Form(type_id) => { let typ = self .type_space .get_type(type_id) @@ -593,19 +594,6 @@ impl Generator { .parameter_ident_with_lifetime("a"); quote! { #name: #typ} } - OperationParameterType::Form(keys) => { - let ts = TokenStream::from_iter( - itertools::Itertools::intersperse( - keys.iter().map(|form_prop_name| { - let form_prop_name = - format_ident!("{}", form_prop_name); - quote! { #form_prop_name: Vec } - }), - quote! {, }, - ), - ); - ts - } OperationParameterType::RawBody => { quote! { #name: B } } @@ -935,15 +923,11 @@ impl Generator { OperationParameterKind::Body( BodyContentType::FormData ), - OperationParameterType::Form(map), + OperationParameterType::Form(_), ) => { - let form_prop_names = map.iter().cloned().map(|form_prop_name| { - let ident= format_ident!("{}", form_prop_name); - quote! { (#form_prop_name, #ident) } - }); Some(quote! { // This uses progenitor_client::RequestBuilderExt which sets up a simple form data based on bytes - .form_from_raw(vec![ #(#form_prop_names),* ])? + .form_from_raw(body.as_form())? })}, (OperationParameterKind::Body(_), _) => { unreachable!("invalid body kind/type combination") @@ -1408,7 +1392,8 @@ impl Generator { .params .iter() .map(|param| match ¶m.typ { - OperationParameterType::Type(type_id) => { + OperationParameterType::Type(type_id) + | OperationParameterType::Form(type_id) => { let ty = self.type_space.get_type(type_id)?; // For body parameters only, if there's a builder we'll @@ -1425,10 +1410,6 @@ impl Generator { } } - OperationParameterType::Form(_form) => { - todo!("Form is nit expected here") - } - OperationParameterType::RawBody => { cloneable = false; Ok(quote! { Result }) @@ -1441,7 +1422,8 @@ impl Generator { .params .iter() .map(|param| match ¶m.typ { - OperationParameterType::Type(type_id) => { + OperationParameterType::Type(type_id) + | OperationParameterType::Form(type_id) => { let ty = self.type_space.get_type(type_id)?; let details = ty.details(); let optional = @@ -1460,9 +1442,6 @@ impl Generator { Ok(quote! { Err(#err_msg.to_string()) }) } } - OperationParameterType::Form(_form) => { - todo!("Form is nit expected here") - } OperationParameterType::RawBody => { let err_msg = format!("{} was not initialized", param.name); Ok(quote! { Err(#err_msg.to_string()) }) @@ -1474,7 +1453,8 @@ impl Generator { .params .iter() .map(|param| match ¶m.typ { - OperationParameterType::Type(type_id) => { + OperationParameterType::Type(type_id) + | OperationParameterType::Form(type_id) => { let ty = self.type_space.get_type(type_id)?; if ty.builder().is_some() { let type_name = ty.ident(); @@ -1488,14 +1468,6 @@ impl Generator { } } - OperationParameterType::Form(_form) => { - todo!("Form is nit expected here") - } - - OperationParameterType::Form(_form) => { - todo!("Form is nit expected here") - } - OperationParameterType::RawBody => Ok(quote! {}), }) .collect::>>()?; @@ -1508,11 +1480,12 @@ impl Generator { .map(|param| { let param_name = format_ident!("{}", param.name); match ¶m.typ { - OperationParameterType::Type(type_id) => { + OperationParameterType::Type(type_id) + | OperationParameterType::Form(type_id) => { let ty = self.type_space.get_type(type_id)?; let details = ty.details(); match (&details, ty.builder()) { - // TODO right now optional body paramters are not + // TODO right now optional body parameters are not // addressed (typify::TypeDetails::Option(_), Some(_)) => { unreachable!() @@ -1596,7 +1569,7 @@ impl Generator { } } - OperationParameterType::Form(form_keys) => { + OperationParameterType::Form(type_id) => { let err_msg = format!( "conversion to `reqwest::Body` for {} failed", param.name, @@ -2161,7 +2134,19 @@ impl Generator { schema ))), }?; - OperationParameterType::Form(mapped) + + let form_name = sanitize( + &format!( + "{}-form", + operation.operation_id.as_ref().unwrap(), + ), + Case::Pascal, + ); + let type_id = self + .type_space + .add_type_with_name(&schema.to_schema(), Some(form_name))?; + self.forms.insert(type_id.clone()); + OperationParameterType::Form(type_id) } BodyContentType::Json | BodyContentType::FormUrlencoded => { // TODO it would be legal to have the encoding field set for