Skip to content

Commit 5963f06

Browse files
authored
Encode values passed to Parts enums (#95)
This commit updates the api_generator to emit code to percent encode values passed to Parts enums when calling the url() function. A value passed to a Part may be one that, if left unencoded, would be interpreted differently by the HTTP client e.g. a # (start of Fragment identifier) or ? (start of Query identifier).
1 parent 7bac8e0 commit 5963f06

29 files changed

+1504
-712
lines changed

api_generator/src/api_generator/code_gen/mod.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,20 @@ use std::str;
1212
/// use declarations common across builders
1313
pub fn use_declarations() -> Tokens {
1414
quote!(
15-
#[allow(unused_imports)]
15+
#![allow(unused_imports)]
1616
use crate::{
1717
client::{Elasticsearch},
1818
params::*,
1919
error::Error,
2020
http::{
2121
headers::{HeaderName, HeaderMap, HeaderValue, CONTENT_TYPE, ACCEPT},
2222
Method,
23-
request::{Body, NdBody, JsonBody},
23+
request::{Body, NdBody, JsonBody, PARTS_ENCODED},
2424
response::Response,
2525
},
2626
};
2727
use std::borrow::Cow;
28+
use percent_encoding::percent_encode;
2829
use serde::Serialize;
2930
)
3031
}

api_generator/src/api_generator/code_gen/url/enum_builder.rs

+8-5
Original file line numberDiff line numberDiff line change
@@ -388,20 +388,23 @@ mod tests {
388388
SearchParts::None => "/_search".into(),
389389
SearchParts::Index(ref index) => {
390390
let index_str = index.join(",");
391-
let mut p = String::with_capacity(9usize + index_str.len());
391+
let encoded_index: Cow<str> = percent_encode(index_str.as_bytes(), PARTS_ENCODED).into();
392+
let mut p = String::with_capacity(9usize + encoded_index.len());
392393
p.push_str("/");
393-
p.push_str(index_str.as_ref());
394+
p.push_str(encoded_index.as_ref());
394395
p.push_str("/_search");
395396
p.into()
396397
}
397398
SearchParts::IndexType(ref index, ref ty) => {
398399
let index_str = index.join(",");
399400
let ty_str = ty.join(",");
400-
let mut p = String::with_capacity(10usize + index_str.len() + ty_str.len());
401+
let encoded_index: Cow<str> = percent_encode(index_str.as_bytes(), PARTS_ENCODED).into();
402+
let encoded_ty: Cow<str> = percent_encode(ty_str.as_bytes(), PARTS_ENCODED).into();
403+
let mut p = String::with_capacity(10usize + encoded_index.len() + encoded_ty.len());
401404
p.push_str("/");
402-
p.push_str(index_str.as_ref());
405+
p.push_str(encoded_index.as_ref());
403406
p.push_str("/");
404-
p.push_str(ty_str.as_ref());
407+
p.push_str(encoded_ty.as_ref());
405408
p.push_str("/_search");
406409
p.into()
407410
}

api_generator/src/api_generator/code_gen/url/url_builder.rs

+65-23
Original file line numberDiff line numberDiff line change
@@ -153,24 +153,28 @@ impl<'a> UrlBuilder<'a> {
153153

154154
/// Build the AST for an allocated url from the path literals and params.
155155
fn build_owned(self) -> syn::Block {
156-
let lit_len_expr = Self::literal_length_expr(&self.path);
156+
157157

158158
// collection of let {name}_str = [self.]{name}.[join(",")|to_string()];
159159
let let_params_exprs = Self::let_parameters_exprs(&self.path, &self.parts);
160160

161-
let mut params_len_exprs = Self::parameter_length_exprs(&self.path, self.parts);
162-
163-
let mut len_exprs = vec![lit_len_expr];
164-
len_exprs.append(&mut params_len_exprs);
165-
let len_expr = Self::summed_length_expr(len_exprs);
161+
let mut let_encoded_params_exprs = Self::let_encoded_exprs(&self.path, &self.parts);
166162

167163
let url_ident = ident("p");
164+
let len_expr = {
165+
let lit_len_expr = Self::literal_length_expr(&self.path);
166+
let mut params_len_exprs = Self::parameter_length_exprs(&self.path);
167+
let mut len_exprs = vec![lit_len_expr];
168+
len_exprs.append(&mut params_len_exprs);
169+
Self::summed_length_expr(len_exprs)
170+
};
168171
let let_stmt = Self::let_p_stmt(url_ident.clone(), len_expr);
169172

170-
let mut push_stmts = Self::push_str_stmts(url_ident.clone(), &self.path, self.parts);
173+
let mut push_stmts = Self::push_str_stmts(url_ident.clone(), &self.path);
171174
let return_expr = syn::Stmt::Expr(Box::new(parse_expr(quote!(#url_ident.into()))));
172175

173176
let mut stmts = let_params_exprs;
177+
stmts.append(&mut let_encoded_params_exprs);
174178
stmts.push(let_stmt);
175179
stmts.append(&mut push_stmts);
176180
stmts.push(return_expr);
@@ -206,6 +210,55 @@ impl<'a> UrlBuilder<'a> {
206210
syn::ExprKind::Lit(syn::Lit::Int(len as u64, syn::IntTy::Usize)).into()
207211
}
208212

213+
/// Creates the AST for a let expression to percent encode path parts
214+
fn let_encoded_exprs(
215+
url: &[PathPart<'a>],
216+
parts: &BTreeMap<String, Type>,
217+
) -> Vec<syn::Stmt> {
218+
url.iter()
219+
.filter_map(|p| match *p {
220+
PathPart::Param(p) => {
221+
let name = valid_name(p);
222+
let path_expr = match &parts[p].ty {
223+
TypeKind::String => path_none(name).into_expr(),
224+
_ => path_none(format!("{}_str", name).as_str()).into_expr()
225+
};
226+
227+
let encoded_ident = ident(format!("encoded_{}", name));
228+
let percent_encode_call: syn::Expr = syn::ExprKind::Call(
229+
Box::new(path_none("percent_encode").into_expr()),
230+
vec![
231+
syn::ExprKind::MethodCall(
232+
ident("as_bytes"),
233+
vec![],
234+
vec![path_expr]
235+
).into(),
236+
path_none("PARTS_ENCODED").into_expr()
237+
],
238+
).into();
239+
240+
let into_call: syn::Expr = syn::ExprKind::MethodCall(
241+
ident("into"),
242+
vec![],
243+
vec![percent_encode_call]
244+
).into();
245+
246+
Some(syn::Stmt::Local(Box::new(syn::Local {
247+
pat: Box::new(syn::Pat::Ident(
248+
syn::BindingMode::ByValue(syn::Mutability::Immutable),
249+
encoded_ident,
250+
None,
251+
)),
252+
ty: Some(Box::new(ty_path("Cow", vec![], vec![ty("str")]))),
253+
init: Some(Box::new(into_call)),
254+
attrs: vec![],
255+
})))
256+
}
257+
_ => None,
258+
})
259+
.collect()
260+
}
261+
209262
/// Creates the AST for a let expression for path parts
210263
fn let_parameters_exprs(
211264
url: &[PathPart<'a>],
@@ -216,8 +269,7 @@ impl<'a> UrlBuilder<'a> {
216269
PathPart::Param(p) => {
217270
let name = valid_name(p);
218271
let name_ident = ident(&name);
219-
let k = p.to_string();
220-
let ty = &parts[&k].ty;
272+
let ty = &parts[p].ty;
221273

222274
// don't generate an assignment expression for strings
223275
if ty == &TypeKind::String {
@@ -279,21 +331,16 @@ impl<'a> UrlBuilder<'a> {
279331
/// Get an expression to find the number of chars in each parameter part for the url.
280332
fn parameter_length_exprs(
281333
url: &[PathPart<'a>],
282-
parts: &BTreeMap<String, Type>,
283334
) -> Vec<syn::Expr> {
284335
url.iter()
285336
.filter_map(|p| match *p {
286337
PathPart::Param(p) => {
287-
let name = match parts[&p.to_string()].ty {
288-
TypeKind::String => valid_name(p).into(),
289-
// handle lists and enums
290-
_ => format!("{}_str", valid_name(p)),
291-
};
338+
let name = format!("encoded_{}", valid_name(p));
292339
Some(
293340
syn::ExprKind::MethodCall(
294341
ident("len"),
295342
vec![],
296-
vec![syn::ExprKind::Path(None, path_none(name.as_ref())).into()],
343+
vec![path_none(name.as_ref()).into_expr()],
297344
)
298345
.into(),
299346
)
@@ -351,8 +398,7 @@ impl<'a> UrlBuilder<'a> {
351398
/// Get a list of statements that append each part to a `String` in order.
352399
fn push_str_stmts(
353400
url_ident: syn::Ident,
354-
url: &[PathPart<'a>],
355-
parts: &BTreeMap<String, Type>,
401+
url: &[PathPart<'a>]
356402
) -> Vec<syn::Stmt> {
357403
url.iter()
358404
.map(|p| match *p {
@@ -361,11 +407,7 @@ impl<'a> UrlBuilder<'a> {
361407
syn::Stmt::Semi(Box::new(parse_expr(quote!(#url_ident.push_str(#lit)))))
362408
}
363409
PathPart::Param(p) => {
364-
let name = match parts[&p.to_string()].ty {
365-
TypeKind::String => valid_name(p).into(),
366-
// handle lists and enums
367-
_ => format!("{}_str", valid_name(p)),
368-
};
410+
let name = format!("encoded_{}", valid_name(p));
369411
let ident = ident(name);
370412
syn::Stmt::Semi(Box::new(parse_expr(
371413
quote!(#url_ident.push_str(#ident.as_ref())),

elasticsearch/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ rustls-tls = ["reqwest/rustls-tls"]
2525
base64 = "^0.11"
2626
bytes = "^0.5"
2727
dyn-clone = "~1"
28+
percent-encoding = "2.1.0"
2829
reqwest = { version = "~0.10", default-features = false, features = ["default-tls", "gzip", "json"] }
2930
url = "^2.1"
3031
serde = { version = "~1", features = ["derive"] }

elasticsearch/src/generated/namespace_clients/async_search.rs

+13-8
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,19 @@
1414
// cargo run -p api_generator
1515
//
1616
// -----------------------------------------------
17-
#[allow(unused_imports)]
17+
#![allow(unused_imports)]
1818
use crate::{
1919
client::Elasticsearch,
2020
error::Error,
2121
http::{
2222
headers::{HeaderMap, HeaderName, HeaderValue, ACCEPT, CONTENT_TYPE},
23-
request::{Body, JsonBody, NdBody},
23+
request::{Body, JsonBody, NdBody, PARTS_ENCODED},
2424
response::Response,
2525
Method,
2626
},
2727
params::*,
2828
};
29+
use percent_encoding::percent_encode;
2930
use serde::Serialize;
3031
use std::borrow::Cow;
3132
#[derive(Debug, Clone, PartialEq)]
@@ -39,9 +40,10 @@ impl<'b> AsyncSearchDeleteParts<'b> {
3940
pub fn url(self) -> Cow<'static, str> {
4041
match self {
4142
AsyncSearchDeleteParts::Id(ref id) => {
42-
let mut p = String::with_capacity(15usize + id.len());
43+
let encoded_id: Cow<str> = percent_encode(id.as_bytes(), PARTS_ENCODED).into();
44+
let mut p = String::with_capacity(15usize + encoded_id.len());
4345
p.push_str("/_async_search/");
44-
p.push_str(id.as_ref());
46+
p.push_str(encoded_id.as_ref());
4547
p.into()
4648
}
4749
}
@@ -155,9 +157,10 @@ impl<'b> AsyncSearchGetParts<'b> {
155157
pub fn url(self) -> Cow<'static, str> {
156158
match self {
157159
AsyncSearchGetParts::Id(ref id) => {
158-
let mut p = String::with_capacity(15usize + id.len());
160+
let encoded_id: Cow<str> = percent_encode(id.as_bytes(), PARTS_ENCODED).into();
161+
let mut p = String::with_capacity(15usize + encoded_id.len());
159162
p.push_str("/_async_search/");
160-
p.push_str(id.as_ref());
163+
p.push_str(encoded_id.as_ref());
161164
p.into()
162165
}
163166
}
@@ -305,9 +308,11 @@ impl<'b> AsyncSearchSubmitParts<'b> {
305308
AsyncSearchSubmitParts::None => "/_async_search".into(),
306309
AsyncSearchSubmitParts::Index(ref index) => {
307310
let index_str = index.join(",");
308-
let mut p = String::with_capacity(15usize + index_str.len());
311+
let encoded_index: Cow<str> =
312+
percent_encode(index_str.as_bytes(), PARTS_ENCODED).into();
313+
let mut p = String::with_capacity(15usize + encoded_index.len());
309314
p.push_str("/");
310-
p.push_str(index_str.as_ref());
315+
p.push_str(encoded_index.as_ref());
311316
p.push_str("/_async_search");
312317
p.into()
313318
}

0 commit comments

Comments
 (0)