Skip to content

Commit 16c7571

Browse files
author
daniel.eades
committed
fix: support httpmock generation for array types
1 parent 4ae8c04 commit 16c7571

20 files changed

+2103
-303
lines changed

Cargo.lock

Lines changed: 714 additions & 63 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ futures = "0.3.31"
3232
futures-core = "0.3.31"
3333
heck = "0.5.0"
3434
http = "1.4.0"
35+
httpmock = "0.7.0"
3536
hyper = "1.8.1"
3637
indexmap = "2.12.1"
3738
openapiv3 = "2.2.0"

progenitor-client/src/progenitor_client.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,19 @@ pub fn encode_path(pc: &str) -> String {
526526
percent_encoding::utf8_percent_encode(pc, PATH_SET).to_string()
527527
}
528528

529+
#[doc(hidden)]
530+
pub fn query_param_pairs<T>(name: &str, value: &T) -> Result<Vec<(String, String)>, String>
531+
where
532+
T: Serialize,
533+
{
534+
let query = serde_urlencoded::to_string(QueryParam::new(name, value))
535+
.map_err(|err| err.to_string())?;
536+
if query.is_empty() {
537+
return Ok(Vec::new());
538+
}
539+
serde_urlencoded::from_str::<Vec<(String, String)>>(&query).map_err(|err| err.to_string())
540+
}
541+
529542
#[doc(hidden)]
530543
pub trait RequestBuilderExt<E> {
531544
fn form_urlencoded<T: Serialize + ?Sized>(self, body: &T) -> Result<RequestBuilder, Error<E>>;

progenitor-impl/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ dropshot = { workspace = true }
2828
expectorate = { workspace = true }
2929
futures = { workspace = true }
3030
http = { workspace = true }
31+
httpmock = { workspace = true }
3132
hyper = { workspace = true }
3233
progenitor-client = { workspace = true }
3334
reqwest = { workspace = true }

progenitor-impl/src/httpmock.rs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,16 @@ impl Generator {
8787
8888
use #crate_path::*;
8989

90+
fn apply_query_param_pairs(
91+
mut when: ::httpmock::When,
92+
pairs: &[(String, String)],
93+
) -> ::httpmock::When {
94+
for (key, value) in pairs {
95+
when = when.query_param(key, value);
96+
}
97+
when
98+
}
99+
90100
#(
91101
pub struct #when(::httpmock::When);
92102
#when_impl
@@ -189,7 +199,12 @@ impl Generator {
189199
OperationParameterKind::Query(true) => (
190200
true,
191201
quote! {
192-
Self(self.0.query_param(#api_name, value.to_string()))
202+
let expected_pairs = ::progenitor_client::query_param_pairs(
203+
#api_name,
204+
&value,
205+
)
206+
.expect("failed to serialize query param");
207+
Self(apply_query_param_pairs(self.0, &expected_pairs))
193208
},
194209
),
195210
OperationParameterKind::Header(true) => (
@@ -203,10 +218,12 @@ impl Generator {
203218
false,
204219
quote! {
205220
if let Some(value) = value.into() {
206-
Self(self.0.query_param(
221+
let expected_pairs = ::progenitor_client::query_param_pairs(
207222
#api_name,
208-
value.to_string(),
209-
))
223+
&value,
224+
)
225+
.expect("failed to serialize query param");
226+
Self(apply_query_param_pairs(self.0, &expected_pairs))
210227
} else {
211228
Self(self.0.matches(|req| {
212229
req.query_params

progenitor-impl/tests/output/src/buildomat_httpmock.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@ pub mod operations {
44
#![doc = r" its inner type with a call to `into_inner()`. This can"]
55
#![doc = r" be used to explicitly deviate from permitted values."]
66
use crate::buildomat_builder::*;
7+
fn apply_query_param_pairs(
8+
mut when: ::httpmock::When,
9+
pairs: &[(String, String)],
10+
) -> ::httpmock::When {
11+
for (key, value) in pairs {
12+
when = when.query_param(key, value);
13+
}
14+
15+
when
16+
}
17+
718
pub struct ControlHoldWhen(::httpmock::When);
819
impl ControlHoldWhen {
920
pub fn new(inner: ::httpmock::When) -> Self {
@@ -208,7 +219,9 @@ pub mod operations {
208219
T: Into<Option<u32>>,
209220
{
210221
if let Some(value) = value.into() {
211-
Self(self.0.query_param("minseq", value.to_string()))
222+
let expected_pairs = ::progenitor_client::query_param_pairs("minseq", &value)
223+
.expect("failed to serialize query param");
224+
Self(apply_query_param_pairs(self.0, &expected_pairs))
212225
} else {
213226
Self(self.0.matches(|req| {
214227
req.query_params
@@ -761,7 +774,9 @@ pub mod operations {
761774
T: Into<Option<&'a types::GetThingOrThingsId>>,
762775
{
763776
if let Some(value) = value.into() {
764-
Self(self.0.query_param("id", value.to_string()))
777+
let expected_pairs = ::progenitor_client::query_param_pairs("id", &value)
778+
.expect("failed to serialize query param");
779+
Self(apply_query_param_pairs(self.0, &expected_pairs))
765780
} else {
766781
Self(self.0.matches(|req| {
767782
req.query_params

progenitor-impl/tests/output/src/cli_gen_httpmock.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@ pub mod operations {
44
#![doc = r" its inner type with a call to `into_inner()`. This can"]
55
#![doc = r" be used to explicitly deviate from permitted values."]
66
use crate::cli_gen_builder::*;
7+
fn apply_query_param_pairs(
8+
mut when: ::httpmock::When,
9+
pairs: &[(String, String)],
10+
) -> ::httpmock::When {
11+
for (key, value) in pairs {
12+
when = when.query_param(key, value);
13+
}
14+
15+
when
16+
}
17+
718
pub struct UnoWhen(::httpmock::When);
819
impl UnoWhen {
920
pub fn new(inner: ::httpmock::When) -> Self {
@@ -19,7 +30,9 @@ pub mod operations {
1930
}
2031

2132
pub fn gateway(self, value: &str) -> Self {
22-
Self(self.0.query_param("gateway", value.to_string()))
33+
let expected_pairs = ::progenitor_client::query_param_pairs("gateway", &value)
34+
.expect("failed to serialize query param");
35+
Self(apply_query_param_pairs(self.0, &expected_pairs))
2336
}
2437

2538
pub fn body(self, value: &types::UnoBody) -> Self {
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
#[allow(unused_imports)]
2+
use progenitor_client::{encode_path, ClientHooks, OperationInfo, RequestBuilderExt};
3+
#[allow(unused_imports)]
4+
pub use progenitor_client::{ByteStream, ClientInfo, Error, ResponseValue};
5+
/// Types used as operation parameters and responses.
6+
#[allow(clippy::all)]
7+
pub mod types {
8+
/// Error types.
9+
pub mod error {
10+
/// Error from a `TryFrom` or `FromStr` implementation.
11+
pub struct ConversionError(::std::borrow::Cow<'static, str>);
12+
impl ::std::error::Error for ConversionError {}
13+
impl ::std::fmt::Display for ConversionError {
14+
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> {
15+
::std::fmt::Display::fmt(&self.0, f)
16+
}
17+
}
18+
19+
impl ::std::fmt::Debug for ConversionError {
20+
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> {
21+
::std::fmt::Debug::fmt(&self.0, f)
22+
}
23+
}
24+
25+
impl From<&'static str> for ConversionError {
26+
fn from(value: &'static str) -> Self {
27+
Self(value.into())
28+
}
29+
}
30+
31+
impl From<String> for ConversionError {
32+
fn from(value: String) -> Self {
33+
Self(value.into())
34+
}
35+
}
36+
}
37+
}
38+
39+
#[derive(Clone, Debug)]
40+
///Client for httpmock-query-array
41+
///
42+
///Version: 0.0.0
43+
pub struct Client {
44+
pub(crate) baseurl: String,
45+
pub(crate) client: reqwest::Client,
46+
}
47+
48+
impl Client {
49+
/// Create a new client.
50+
///
51+
/// `baseurl` is the base URL provided to the internal
52+
/// `reqwest::Client`, and should include a scheme and hostname,
53+
/// as well as port and a path stem if applicable.
54+
pub fn new(baseurl: &str) -> Self {
55+
#[cfg(not(target_arch = "wasm32"))]
56+
let client = {
57+
let dur = ::std::time::Duration::from_secs(15u64);
58+
reqwest::ClientBuilder::new()
59+
.connect_timeout(dur)
60+
.timeout(dur)
61+
};
62+
#[cfg(target_arch = "wasm32")]
63+
let client = reqwest::ClientBuilder::new();
64+
Self::new_with_client(baseurl, client.build().unwrap())
65+
}
66+
67+
/// Construct a new client with an existing `reqwest::Client`,
68+
/// allowing more control over its configuration.
69+
///
70+
/// `baseurl` is the base URL provided to the internal
71+
/// `reqwest::Client`, and should include a scheme and hostname,
72+
/// as well as port and a path stem if applicable.
73+
pub fn new_with_client(baseurl: &str, client: reqwest::Client) -> Self {
74+
Self {
75+
baseurl: baseurl.to_string(),
76+
client,
77+
}
78+
}
79+
}
80+
81+
impl ClientInfo<()> for Client {
82+
fn api_version() -> &'static str {
83+
"0.0.0"
84+
}
85+
86+
fn baseurl(&self) -> &str {
87+
self.baseurl.as_str()
88+
}
89+
90+
fn client(&self) -> &reqwest::Client {
91+
&self.client
92+
}
93+
94+
fn inner(&self) -> &() {
95+
&()
96+
}
97+
}
98+
99+
impl ClientHooks<()> for &Client {}
100+
impl Client {
101+
///Sends a `GET` request to `/widgets`
102+
///
103+
///```ignore
104+
/// let response = client.list_widgets()
105+
/// .tags(tags)
106+
/// .send()
107+
/// .await;
108+
/// ```
109+
pub fn list_widgets(&self) -> builder::ListWidgets<'_> {
110+
builder::ListWidgets::new(self)
111+
}
112+
}
113+
114+
/// Types for composing operation parameters.
115+
#[allow(clippy::all)]
116+
pub mod builder {
117+
use super::types;
118+
#[allow(unused_imports)]
119+
use super::{
120+
encode_path, ByteStream, ClientHooks, ClientInfo, Error, OperationInfo, RequestBuilderExt,
121+
ResponseValue,
122+
};
123+
///Builder for [`Client::list_widgets`]
124+
///
125+
///[`Client::list_widgets`]: super::Client::list_widgets
126+
#[derive(Debug, Clone)]
127+
pub struct ListWidgets<'a> {
128+
client: &'a super::Client,
129+
tags: Result<::std::vec::Vec<::std::string::String>, String>,
130+
}
131+
132+
impl<'a> ListWidgets<'a> {
133+
pub fn new(client: &'a super::Client) -> Self {
134+
Self {
135+
client: client,
136+
tags: Err("tags was not initialized".to_string()),
137+
}
138+
}
139+
140+
pub fn tags<V>(mut self, value: V) -> Self
141+
where
142+
V: std::convert::TryInto<::std::vec::Vec<::std::string::String>>,
143+
{
144+
self.tags = value.try_into().map_err(|_| {
145+
"conversion to `:: std :: vec :: Vec < :: std :: string :: String >` for tags \
146+
failed"
147+
.to_string()
148+
});
149+
self
150+
}
151+
152+
///Sends a `GET` request to `/widgets`
153+
pub async fn send(self) -> Result<ResponseValue<()>, Error<()>> {
154+
let Self { client, tags } = self;
155+
let tags = tags.map_err(Error::InvalidRequest)?;
156+
let url = format!("{}/widgets", client.baseurl,);
157+
let mut header_map = ::reqwest::header::HeaderMap::with_capacity(1usize);
158+
header_map.append(
159+
::reqwest::header::HeaderName::from_static("api-version"),
160+
::reqwest::header::HeaderValue::from_static(super::Client::api_version()),
161+
);
162+
#[allow(unused_mut)]
163+
let mut request = client
164+
.client
165+
.get(url)
166+
.query(&progenitor_client::QueryParam::new("tags", &tags))
167+
.headers(header_map)
168+
.build()?;
169+
let info = OperationInfo {
170+
operation_id: "list_widgets",
171+
};
172+
client.pre(&mut request, &info).await?;
173+
let result = client.exec(request, &info).await;
174+
client.post(&result, &info).await?;
175+
let response = result?;
176+
match response.status().as_u16() {
177+
204u16 => Ok(ResponseValue::empty(response)),
178+
_ => Err(Error::UnexpectedResponse(response)),
179+
}
180+
}
181+
}
182+
}
183+
184+
/// Items consumers will typically use such as the Client.
185+
pub mod prelude {
186+
pub use self::super::Client;
187+
}

0 commit comments

Comments
 (0)