Skip to content

Commit 96189a5

Browse files
authored
Add bytes method to Response struct (#164)
* Use a conventional struct instead of a Tuple struct The Response struct contains a reqwest::Request and a Method fields, and accessing them by position may be a bit confusing. Giving an actual name to the fields brings us the benefit of easily understanding the methods we are reading, without the need to go to the top to remember which field we are referring to. Reference: https://doc.rust-lang.org/1.9.0/book/structs.html#tuple-structs * Expose `bytes` to the Response object This patch exposes the `Response::bytes`, which is basically a wrapper on reqwest::Response::bytes(). * Add additional note on vm.max_map_count settings Closes #160
1 parent 07b5add commit 96189a5

File tree

3 files changed

+80
-35
lines changed

3 files changed

+80
-35
lines changed

CONTRIBUTING.md

+25-22
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ to address it, please assign the issue to yourself, so that others know that you
1010

1111
## Sign the Contributor License Agreement
1212

13-
We do ask that you sign the [Contiributor License Agreement](https://www.elastic.co/contributor-agreement)
13+
We do ask that you sign the [Contiributor License Agreement](https://www.elastic.co/contributor-agreement)
1414
before we can accept pull requests from you.
1515

1616
## Development
@@ -23,21 +23,24 @@ The project makes use of the following, which should be installed
2323

2424
- [**Docker**](https://www.docker.com/)
2525

26-
Docker is used to start instances of Elasticsearch by using
26+
Docker is used to start instances of Elasticsearch by using
2727
[Elastic's Elasticsearch docker images](https://container-library.elastic.co/).
2828
For Windows, use [Docker with WSL 2 backend](https://docs.docker.com/docker-for-windows/wsl/).
29-
29+
3030
- [**Cargo make**](https://sagiegurari.github.io/cargo-make/)
3131

32-
Cargo make is used to define and configure a set of tasks, and run them as a flow. This helps with performing actions
32+
Cargo make is used to define and configure a set of tasks, and run them as a flow. This helps with performing actions
3333
such as starting an Elasticsearch instance for integration tests
34-
34+
3535
Cargo make can be installed with
36-
36+
3737
```sh
3838
cargo install --force cargo-make
3939
```
40-
40+
41+
42+
If you are running the tests in Docker, [set `vm.max_map_count` for your platform](https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html#_set_vm_max_map_count_to_at_least_262144) to allow Elasticsearch to start.
43+
4144
### Cargo make
4245

4346
Cargo make is used to define and configure a set of tasks, and run them as a flow. To see all of the Elasticsearch
@@ -64,7 +67,7 @@ The `Elasticsearch` category of steps are specifically defined for this project
6467

6568
- Run Elasticsearch package tests
6669

67-
Optionally pass
70+
Optionally pass
6871

6972
- `STACK_VERSION`: Elasticsearch version like `7.9.0` or can be
7073
a snapshot release like `7.x-SNAPSHOT`
@@ -75,12 +78,12 @@ The `Elasticsearch` category of steps are specifically defined for this project
7578

7679
- Run YAML tests
7780

78-
Optionally pass
81+
Optionally pass
7982

8083
- `STACK_VERSION`: Elasticsearch version like `7.9.0` or can be
8184
a snapshot release like `7.x-SNAPSHOT`
8285
- `TEST_SUITE`: Elasticsearch distribution of `free` or `platinum`
83-
86+
8487
```sh
8588
cargo make test-yaml --env STACK_VERSION=7.9.0 --env TEST_SUITE=free
8689
```
@@ -96,9 +99,9 @@ the root client, `Elasticsearch`, or on one of the _namespaced clients_, such as
9699
are based on the grouping of APIs within the [Elasticsearch](https://github.com/elastic/elasticsearch/tree/master/rest-api-spec) and [X-Pack](https://github.com/elastic/elasticsearch/tree/master/x-pack/plugin/src/test/resources/rest-api-spec/api) REST API specs from which much of the client is generated.
97100
All API functions are `async` only, and can be `await`ed.
98101

99-
- #### `api_generator`
102+
- #### `api_generator`
100103

101-
A small executable that downloads REST API specs from GitHub and generates much of the client package from the specs.
104+
A small executable that downloads REST API specs from GitHub and generates much of the client package from the specs.
102105
The minimum REST API spec version compatible with the generator is `v7.4.0`.
103106

104107
The `api_generator` package makes heavy use of the [`syn`](https://docs.rs/syn/1.0.5/syn/) and [`quote`](https://docs.rs/quote/1.0.2/quote/) crates to generate Rust code from the REST API specs.
@@ -110,11 +113,11 @@ can be `to_string()`'ed and written to disk, and this is used to create much of
110113

111114
A small executable that downloads YAML tests from GitHub and generates client tests from the YAML tests. The
112115
version of YAML tests to download are determined from the commit hash of a running Elasticsearch instance.
113-
116+
114117
The `yaml_test_runner` package can be run with `cargo make test-yaml` to run the generated client tests,
115118
passing environment variables `TEST_SUITE` and `STACK_VERSION` to control the distribution and version,
116119
respectively.
117-
120+
118121
### Design principles
119122

120123
1. Generate as much of the client as feasible from the REST API specs
@@ -124,8 +127,8 @@ can be `to_string()`'ed and written to disk, and this is used to create much of
124127
- accepted HTTP methods e.g. `GET`, `POST`
125128
- the URL query string parameters
126129
- whether the API accepts a body
127-
128-
2. Prefer generation methods that produce ASTs and token streams over strings.
130+
131+
2. Prefer generation methods that produce ASTs and token streams over strings.
129132
The `quote` and `syn` crates help
130133

131134
3. Get it working, then refine/refactor
@@ -134,14 +137,14 @@ The `quote` and `syn` crates help
134137
- Design of the API is conducive to ease of use
135138
- Asynchronous only
136139
- Control API invariants through arguments on API function. For example
137-
140+
138141
```no_run
139142
client.delete_script(DeleteScriptParts::Id("script_id"))
140143
.send()
141144
.await?;
142145
```
143-
144-
An id must always be provided for a delete script API call, so the `delete_script()` function
146+
147+
An id must always be provided for a delete script API call, so the `delete_script()` function
145148
must accept it as a value.
146149
147150
### Coding style guide
@@ -172,7 +175,7 @@ cargo make clippy
172175

173176
### Running MSVC debugger in VS Code
174177

175-
From [Bryce Van Dyk's blog post](https://www.brycevandyk.com/debug-rust-on-windows-with-visual-studio-code-and-the-msvc-debugger/),
178+
From [Bryce Van Dyk's blog post](https://www.brycevandyk.com/debug-rust-on-windows-with-visual-studio-code-and-the-msvc-debugger/),
176179
if wishing to use the MSVC debugger with Rust in VS code, which may be preferred on Windows
177180

178181
1. Install [C/C++ VS Code extensions](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools)
@@ -182,7 +185,7 @@ if wishing to use the MSVC debugger with Rust in VS code, which may be preferred
182185
```json
183186
{
184187
"version": "0.2.0",
185-
"configurations": [
188+
"configurations": [
186189
{
187190
"name": "Debug api_generator",
188191
"type": "cppvsdbg",
@@ -197,5 +200,5 @@ if wishing to use the MSVC debugger with Rust in VS code, which may be preferred
197200
]
198201
}
199202
```
200-
203+
201204
3. Add `"debug.allowBreakpointsEverywhere": true` to VS code settings.json

elasticsearch/src/http/response.rs

+28-13
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use crate::{
2121
error::Error as ClientError,
2222
http::{headers::HeaderMap, Method, StatusCode, Url},
2323
};
24+
use bytes::Bytes;
2425
use serde::{
2526
de,
2627
de::{DeserializeOwned, MapAccess, Visitor},
@@ -31,12 +32,18 @@ use std::{collections::BTreeMap, fmt, str::FromStr};
3132
use void::Void;
3233

3334
/// A response from Elasticsearch
34-
pub struct Response(reqwest::Response, Method);
35+
pub struct Response {
36+
response: reqwest::Response,
37+
method: Method,
38+
}
3539

3640
impl Response {
3741
/// Creates a new instance of an Elasticsearch response
3842
pub fn new(response: reqwest::Response, method: Method) -> Self {
39-
Self(response, method)
43+
Self {
44+
response: response,
45+
method: method,
46+
}
4047
}
4148

4249
/// Get the response content-length, if known.
@@ -47,12 +54,12 @@ impl Response {
4754
/// - The response is compressed and automatically decoded (thus changing
4855
/// the actual decoded length).
4956
pub fn content_length(&self) -> Option<u64> {
50-
self.0.content_length()
57+
self.response.content_length()
5158
}
5259

5360
/// Gets the response content-type.
5461
pub fn content_type(&self) -> &str {
55-
self.0
62+
self.response
5663
.headers()
5764
.get(crate::http::headers::CONTENT_TYPE)
5865
.and_then(|value| value.to_str().ok())
@@ -61,15 +68,15 @@ impl Response {
6168

6269
/// Turn the response into an [Error] if Elasticsearch returned an error.
6370
pub fn error_for_status_code(self) -> Result<Self, ClientError> {
64-
match self.0.error_for_status_ref() {
71+
match self.response.error_for_status_ref() {
6572
Ok(_) => Ok(self),
6673
Err(err) => Err(err.into()),
6774
}
6875
}
6976

7077
/// Turn the response into an [Error] if Elasticsearch returned an error.
7178
pub fn error_for_status_code_ref(&self) -> Result<&Self, ClientError> {
72-
match self.0.error_for_status_ref() {
79+
match self.response.error_for_status_ref() {
7380
Ok(_) => Ok(self),
7481
Err(err) => Err(err.into()),
7582
}
@@ -95,44 +102,52 @@ impl Response {
95102
where
96103
B: DeserializeOwned,
97104
{
98-
let body = self.0.json::<B>().await?;
105+
let body = self.response.json::<B>().await?;
99106
Ok(body)
100107
}
101108

102109
/// Gets the response headers.
103110
pub fn headers(&self) -> &HeaderMap {
104-
self.0.headers()
111+
self.response.headers()
105112
}
106113

107114
/// Gets the request method.
108115
pub fn method(&self) -> Method {
109-
self.1
116+
self.method
110117
}
111118

112119
/// Get the HTTP status code of the response
113120
pub fn status_code(&self) -> StatusCode {
114-
self.0.status()
121+
self.response.status()
115122
}
116123

117124
/// Asynchronously reads the response body as plain text
118125
///
119126
/// Reading the response body consumes `self`
120127
pub async fn text(self) -> Result<String, ClientError> {
121-
let body = self.0.text().await?;
128+
let body = self.response.text().await?;
122129
Ok(body)
123130
}
124131

132+
/// Asynchronously reads the response body as bytes
133+
///
134+
/// Reading the response body consumes `self`
135+
pub async fn bytes(self) -> Result<Bytes, ClientError> {
136+
let bytes: Bytes = self.response.bytes().await?;
137+
Ok(bytes)
138+
}
139+
125140
/// Gets the request URL
126141
pub fn url(&self) -> &Url {
127-
self.0.url()
142+
self.response.url()
128143
}
129144

130145
/// Gets the Deprecation warning response headers
131146
///
132147
/// Deprecation headers signal the use of Elasticsearch functionality
133148
/// or features that are deprecated and will be removed in a future release.
134149
pub fn warning_headers(&self) -> impl Iterator<Item = &str> {
135-
self.0.headers().get_all("Warning").iter().map(|w| {
150+
self.response.headers().get_all("Warning").iter().map(|w| {
136151
let s = w.to_str().unwrap();
137152
let first_quote = s.find('"').unwrap();
138153
let last_quote = s.len() - 1;

elasticsearch/tests/client.rs

+27
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ use elasticsearch::{
3232
};
3333

3434
use crate::common::client::index_documents;
35+
use bytes::Bytes;
3536
use hyper::Method;
3637
use serde_json::{json, Value};
3738
use std::time::Duration;
@@ -309,6 +310,32 @@ async fn search_with_no_body() -> Result<(), failure::Error> {
309310
Ok(())
310311
}
311312

313+
#[tokio::test]
314+
async fn read_response_as_bytes() -> Result<(), failure::Error> {
315+
let client = client::create_default();
316+
let _ = index_documents(&client).await?;
317+
let response = client
318+
.search(SearchParts::None)
319+
.pretty(true)
320+
.q("title:Elasticsearch")
321+
.send()
322+
.await?;
323+
324+
assert_eq!(response.status_code(), StatusCode::OK);
325+
assert_eq!(response.method(), elasticsearch::http::Method::Get);
326+
327+
let response: Bytes = response.bytes().await?;
328+
let json: Value = serde_json::from_slice(&response).unwrap();
329+
330+
assert!(json["took"].as_i64().is_some());
331+
332+
for hit in json["hits"]["hits"].as_array().unwrap() {
333+
assert!(hit["_source"]["title"].as_str().is_some());
334+
}
335+
336+
Ok(())
337+
}
338+
312339
#[tokio::test]
313340
async fn cat_health_format_json() -> Result<(), failure::Error> {
314341
let client = client::create_default();

0 commit comments

Comments
 (0)