-
Notifications
You must be signed in to change notification settings - Fork 71
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
impl "multipart/form-data" support #418
base: main
Are you sure you want to change the base?
Changes from all commits
af28d71
f67be29
8b6f4ef
2ec5033
e2eda20
46a0bfb
cd8f72b
b347eaa
5acad25
1c227b9
2f63cc2
41a421b
7eed031
16ec6a0
4323c11
b57f58a
13b1e05
1707387
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,9 +5,10 @@ authors = ["Adam H. Leventhal <[email protected]>"] | |
edition = "2021" | ||
|
||
[dependencies] | ||
|
||
serde = { version = "1.0", features = ["derive"] } | ||
chrono = { version = "0.4", features = ["serde"] } | ||
progenitor = { path = "../progenitor" } | ||
reqwest = { version = "0.11.27", features = ["json", "stream"] } | ||
reqwest = { version = "0.11.27", features = ["json", "stream", "multipart"] } | ||
schemars = { version = "0.8.16", features = ["uuid1"] } | ||
serde = { version = "1.0", features = ["derive"] } | ||
uuid = { version = "1.8", features = ["serde", "v4"] } |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,7 +8,7 @@ use std::ops::{Deref, DerefMut}; | |
|
||
use bytes::Bytes; | ||
use futures_core::Stream; | ||
use reqwest::RequestBuilder; | ||
use reqwest::{multipart::Part, RequestBuilder}; | ||
use serde::{de::DeserializeOwned, Serialize}; | ||
|
||
#[cfg(not(target_arch = "wasm32"))] | ||
|
@@ -421,11 +421,23 @@ pub fn encode_path(pc: &str) -> String { | |
} | ||
|
||
#[doc(hidden)] | ||
pub trait RequestBuilderExt<E> { | ||
pub trait RequestBuilderExt<E> | ||
where | ||
Self: Sized, | ||
{ | ||
fn form_urlencoded<T: Serialize + ?Sized>( | ||
self, | ||
body: &T, | ||
) -> Result<RequestBuilder, Error<E>>; | ||
|
||
fn form_from_raw< | ||
S: AsRef<str>, | ||
T: AsRef<[u8]>, | ||
I: Sized + IntoIterator<Item = (S, T)>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because a form can contain multiple streams. That's the least limiting for an Impl and sufficient for the |
||
>( | ||
self, | ||
iter: I, | ||
) -> Result<Self, Error<E>>; | ||
} | ||
|
||
impl<E> RequestBuilderExt<E> for RequestBuilder { | ||
|
@@ -440,8 +452,37 @@ impl<E> RequestBuilderExt<E> for RequestBuilder { | |
"application/x-www-form-urlencoded", | ||
), | ||
) | ||
.body(serde_urlencoded::to_string(body).map_err(|_| { | ||
Error::InvalidRequest("failed to serialize body".to_string()) | ||
.body(serde_urlencoded::to_string(body).map_err(|e| { | ||
Error::InvalidRequest(format!( | ||
"failed to serialize body: {e:?}" | ||
)) | ||
})?)) | ||
} | ||
|
||
fn form_from_raw< | ||
S: AsRef<str>, | ||
T: AsRef<[u8]>, | ||
I: Sized + IntoIterator<Item = (S, T)>, | ||
>( | ||
self, | ||
iter: I, | ||
) -> Result<Self, Error<E>> { | ||
use reqwest::multipart::Form; | ||
|
||
let mut form = Form::new(); | ||
for (name, value) in iter { | ||
form = form.part( | ||
name.as_ref().to_owned(), | ||
Part::stream(Vec::from(value.as_ref())), | ||
); | ||
} | ||
Ok(self | ||
.header( | ||
reqwest::header::CONTENT_TYPE, | ||
reqwest::header::HeaderValue::from_static( | ||
"multipart/form-data", | ||
), | ||
) | ||
Comment on lines
+480
to
+485
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is redundant, the |
||
.multipart(form)) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -3,14 +3,19 @@ | |||||
//! Core implementation for the progenitor OpenAPI client generator. | ||||||
|
||||||
#![deny(missing_docs)] | ||||||
#![cfg_attr(not(test), deny(unused_crate_dependencies))] | ||||||
#![deny(unused_import_braces)] | ||||||
#![deny(unused_imports)] | ||||||
|
||||||
use std::collections::{BTreeMap, HashMap, HashSet}; | ||||||
|
||||||
use indexmap::IndexSet; | ||||||
use openapiv3::OpenAPI; | ||||||
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; | ||||||
|
@@ -48,6 +53,7 @@ pub type Result<T> = std::result::Result<T, Error>; | |||||
/// OpenAPI generator. | ||||||
pub struct Generator { | ||||||
type_space: TypeSpace, | ||||||
forms: IndexSet<TypeId>, | ||||||
settings: GenerationSettings, | ||||||
uses_futures: bool, | ||||||
uses_websockets: bool, | ||||||
|
@@ -198,6 +204,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(), | ||||||
|
@@ -240,6 +247,7 @@ impl Generator { | |||||
Self { | ||||||
type_space: TypeSpace::new(&type_settings), | ||||||
settings: settings.clone(), | ||||||
forms: Default::default(), | ||||||
uses_futures: false, | ||||||
uses_websockets: false, | ||||||
} | ||||||
|
@@ -304,6 +312,42 @@ 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 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)| { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The In my case, the OpenAPI spec uses I went through There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if this is the best approach, possibly one of the maintainers could suggest a better one. |
||||||
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( | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
#(#properties,)* below There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The above creates a punctuated list, which can both be used with quote to create the correct expansion, while also being iterable, can change to |
||||||
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<Item=(&'static str, &'f [u8])> { | ||||||
[#properties] | ||||||
.into_iter() | ||||||
.filter_map(|(name, val)| val.as_ref().map(|val| (name, val.as_bytes()))) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When trying to compile I'm getting the following error (rust v1.78.0):
The specification I'm using includes a single property. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I ran into this limitation a few weeks back, and the root cause here being no unified way between That said, It'll be September before I'll be able to work on this PR |
||||||
} | ||||||
} | ||||||
} | ||||||
})); | ||||||
// 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| { | ||||||
|
@@ -332,20 +376,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; | ||||||
|
@@ -373,6 +417,8 @@ impl Generator { | |||||
use std::convert::TryFrom; | ||||||
|
||||||
#types | ||||||
|
||||||
#extra_impl | ||||||
} | ||||||
|
||||||
#[derive(Clone, Debug)] | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
revert?