Skip to content

Commit 1b0cae1

Browse files
NeoLegendsEmpty2k12
authored andcommitted
Use async await (#45)
* Use async await * Fix integration tests * Update the readme to use async await * Indicate minimum required rust version 1.39 due to async / await * Replace RunOnDrop with manual test harness function * Update reqwest to v0.10 * Enable missing tokio features * Fix error handling * Fix docs * Add minimum Rust version to test suite * Document rewrite to async / await * Fix clippy errors * Allow main functions in docs * Reformat code * Use stable clippy and rustfmt
1 parent 04d1fc8 commit 1b0cae1

File tree

9 files changed

+606
-607
lines changed

9 files changed

+606
-607
lines changed

.travis.yml

+3-2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ rust:
2929
- stable
3030
- beta
3131
- nightly
32+
- 1.39.0
3233

3334
matrix:
3435
fast_finish: true
@@ -37,8 +38,8 @@ matrix:
3738
- rust: stable
3839
env: NAME='linting'
3940
before_script:
40-
- rustup component add rustfmt-preview
41-
- rustup component add clippy-preview
41+
- rustup component add rustfmt
42+
- rustup component add clippy
4243
script:
4344
- cargo fmt --all -- --check
4445
- cargo clippy --all-targets --all-features -- -D warnings

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Changed
11+
12+
- Rewrite to `async` / `await`. Rust 1.39 is now the minimum required Rust version.
13+
1014
## [0.0.5] - 2019-08-16
1115

1216
This release removes the prefix `InfluxDb` of most types in this library and reexports the types under the `influxdb::` path. In most cases, you can directly use the types now: e.g. `influxdb::Client` vs `influxdb::client::InfluxDbClient`.

Cargo.toml

+7-5
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,17 @@ repository = "https://github.com/Empty2k12/influxdb-rust"
1414
travis-ci = { repository = "Empty2k12/influxdb-rust", branch = "master" }
1515

1616
[dependencies]
17-
reqwest = "0.9.17"
18-
futures = "0.1.27"
19-
tokio = "0.1.20"
17+
chrono = { version = "0.4.9", optional = true }
2018
failure = "0.1.5"
21-
serde = { version = "1.0.92", optional = true }
19+
futures = "0.3.1"
20+
reqwest = { version = "0.10", features = ["json"] }
21+
serde = { version = "1.0.92", features = ["derive"], optional = true }
2222
serde_json = { version = "1.0", optional = true }
23-
chrono = { version = "0.4.9", optional = true }
2423

2524
[features]
2625
use-serde = ["serde", "serde_json"]
2726
chrono_timestamps = ["chrono"]
2827
default = ["use-serde"]
28+
29+
[dev-dependencies]
30+
tokio = { version = "0.2", features = ["macros"] }

README.md

+7-12
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
<a href="https://www.rust-lang.org/en-US/">
2323
<img src="https://img.shields.io/badge/Made%20with-Rust-orange.svg" alt='Build with Rust' />
2424
</a>
25+
<a href="https://blog.rust-lang.org/2019/11/07/Rust-1.39.0.html">
26+
<img src="https://img.shields.io/badge/rustc-1.39+-yellow.svg" alt='Minimum Rust Version' />
27+
</a>
2528
</p>
2629

2730
This library is a work in progress. Although we've been using it in production at [OpenVelo](https://openvelo.org/),
@@ -54,8 +57,6 @@ For an example with using Serde deserialization, please refer to [serde_integrat
5457

5558
```rust
5659
use influxdb::{Client, Query, Timestamp};
57-
use serde::Deserialize;
58-
use tokio::runtime::current_thread::Runtime;
5960

6061
// Create a Client with URL `http://localhost:8086`
6162
// and database name `test`
@@ -67,21 +68,15 @@ let client = Client::new("http://localhost:8086", "test");
6768
let write_query = Query::write_query(Timestamp::Now, "weather")
6869
.add_field("temperature", 82);
6970

70-
// Since this library is async by default, we're going to need a Runtime,
71-
// which can asynchonously run our query.
72-
// The [tokio](https://crates.io/crates/tokio) crate lets us easily create a new Runtime.
73-
let mut rt = Runtime::new().expect("Unable to create a runtime");
74-
75-
// To actually submit the data to InfluxDB, the `block_on` method can be used to
76-
// halt execution of our program until it has been completed.
77-
let write_result = rt.block_on(client.query(&write_query));
71+
// Submit the query to InfluxDB.
72+
let write_result = client.query(&write_query).await;
7873
assert!(write_result.is_ok(), "Write result was not okay");
7974

8075
// Reading data is as simple as writing. First we need to create a query
8176
let read_query = Query::raw_read_query("SELECT * FROM weather");
8277

83-
// Again, we're blocking until the request is done
84-
let read_result = rt.block_on(client.query(&read_query));
78+
// submit the request and wait until it's done
79+
let read_result = client.query(&read_query).await;
8580

8681
assert!(read_result.is_ok(), "Read result was not ok");
8782

README.tpl

+3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
<a href="https://www.rust-lang.org/en-US/">
2323
<img src="https://img.shields.io/badge/Made%20with-Rust-orange.svg" alt='Build with Rust' />
2424
</a>
25+
<a href="https://blog.rust-lang.org/2019/11/07/Rust-1.39.0.html">
26+
<img src="https://img.shields.io/badge/rustc-1.39+-yellow.svg" alt='Minimum Rust Version' />
27+
</a>
2528
</p>
2629

2730
{{readme}}

src/client/mod.rs

+71-106
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,8 @@
1515
//! assert_eq!(client.database_name(), "test");
1616
//! ```
1717
18-
use futures::{Future, Stream};
19-
use reqwest::r#async::{Client as ReqwestClient, Decoder};
20-
use reqwest::{StatusCode, Url};
21-
22-
use std::mem;
18+
use futures::prelude::*;
19+
use reqwest::{self, Client as ReqwestClient, StatusCode, Url};
2320

2421
use crate::query::QueryTypes;
2522
use crate::Error;
@@ -130,29 +127,27 @@ impl Client {
130127
/// Pings the InfluxDB Server
131128
///
132129
/// Returns a tuple of build type and version number
133-
pub fn ping(&self) -> impl Future<Item = (String, String), Error = Error> + Send {
134-
ReqwestClient::new()
135-
.get(format!("{}/ping", self.url).as_str())
136-
.send()
137-
.map(|res| {
138-
let build = res
139-
.headers()
140-
.get("X-Influxdb-Build")
141-
.unwrap()
142-
.to_str()
143-
.unwrap();
144-
let version = res
145-
.headers()
146-
.get("X-Influxdb-Version")
147-
.unwrap()
148-
.to_str()
149-
.unwrap();
150-
151-
(String::from(build), String::from(version))
152-
})
130+
pub async fn ping(&self) -> Result<(String, String), Error> {
131+
let res = reqwest::get(format!("{}/ping", self.url).as_str())
132+
.await
153133
.map_err(|err| Error::ProtocolError {
154134
error: format!("{}", err),
155-
})
135+
})?;
136+
137+
let build = res
138+
.headers()
139+
.get("X-Influxdb-Build")
140+
.unwrap()
141+
.to_str()
142+
.unwrap();
143+
let version = res
144+
.headers()
145+
.get("X-Influxdb-Version")
146+
.unwrap()
147+
.to_str()
148+
.unwrap();
149+
150+
Ok((build.to_owned(), version.to_owned()))
156151
}
157152

158153
/// Sends a [`ReadQuery`](crate::ReadQuery) or [`WriteQuery`](crate::WriteQuery) to the InfluxDB Server.
@@ -165,56 +160,47 @@ impl Client {
165160
///
166161
/// # Examples
167162
///
168-
/// ```rust
163+
/// ```rust,no_run
169164
/// use influxdb::{Client, Query, Timestamp};
170165
///
166+
/// # #[tokio::main]
167+
/// # async fn main() -> Result<(), failure::Error> {
171168
/// let client = Client::new("http://localhost:8086", "test");
172-
/// let _future = client.query(
173-
/// &Query::write_query(Timestamp::Now, "weather")
174-
/// .add_field("temperature", 82)
175-
/// );
169+
/// let query = Query::write_query(Timestamp::Now, "weather")
170+
/// .add_field("temperature", 82);
171+
/// let results = client.query(&query).await?;
172+
/// # Ok(())
173+
/// # }
176174
/// ```
177175
/// # Errors
178176
///
179177
/// If the function can not finish the query,
180178
/// a [`Error`] variant will be returned.
181179
///
182180
/// [`Error`]: enum.Error.html
183-
pub fn query<'q, Q>(&self, q: &'q Q) -> Box<dyn Future<Item = String, Error = Error> + Send>
181+
pub async fn query<'q, Q>(&self, q: &'q Q) -> Result<String, Error>
184182
where
185183
Q: Query,
186184
&'q Q: Into<QueryTypes<'q>>,
187185
{
188-
use futures::future;
189-
190-
let query = match q.build() {
191-
Err(err) => {
192-
let error = Error::InvalidQueryError {
193-
error: format!("{}", err),
194-
};
195-
return Box::new(future::err::<String, Error>(error));
196-
}
197-
Ok(query) => query,
198-
};
186+
let query = q.build().map_err(|err| Error::InvalidQueryError {
187+
error: format!("{}", err),
188+
})?;
199189

200190
let basic_parameters: Vec<(String, String)> = self.into();
201191

202192
let client = match q.into() {
203193
QueryTypes::Read(_) => {
204194
let read_query = query.get();
205-
let mut url = match Url::parse_with_params(
195+
let mut url = Url::parse_with_params(
206196
format!("{url}/query", url = self.database_url()).as_str(),
207197
basic_parameters,
208-
) {
209-
Ok(url) => url,
210-
Err(err) => {
211-
let error = Error::UrlConstructionError {
212-
error: format!("{}", err),
213-
};
214-
return Box::new(future::err::<String, Error>(error));
215-
}
216-
};
217-
url.query_pairs_mut().append_pair("q", &read_query.clone());
198+
)
199+
.map_err(|err| Error::UrlConstructionError {
200+
error: format!("{}", err),
201+
})?;
202+
203+
url.query_pairs_mut().append_pair("q", &read_query);
218204

219205
if read_query.contains("SELECT") || read_query.contains("SHOW") {
220206
ReqwestClient::new().get(url)
@@ -223,65 +209,44 @@ impl Client {
223209
}
224210
}
225211
QueryTypes::Write(write_query) => {
226-
let mut url = match Url::parse_with_params(
212+
let mut url = Url::parse_with_params(
227213
format!("{url}/write", url = self.database_url()).as_str(),
228214
basic_parameters,
229-
) {
230-
Ok(url) => url,
231-
Err(err) => {
232-
let error = Error::InvalidQueryError {
233-
error: format!("{}", err),
234-
};
235-
return Box::new(future::err::<String, Error>(error));
236-
}
237-
};
215+
)
216+
.map_err(|err| Error::InvalidQueryError {
217+
error: format!("{}", err),
218+
})?;
219+
238220
url.query_pairs_mut()
239221
.append_pair("precision", &write_query.get_precision());
222+
240223
ReqwestClient::new().post(url).body(query.get())
241224
}
242225
};
243-
Box::new(
244-
client
245-
.send()
246-
.map_err(|err| Error::ConnectionError { error: err })
247-
.and_then(
248-
|res| -> future::FutureResult<reqwest::r#async::Response, Error> {
249-
match res.status() {
250-
StatusCode::UNAUTHORIZED => {
251-
futures::future::err(Error::AuthorizationError)
252-
}
253-
StatusCode::FORBIDDEN => {
254-
futures::future::err(Error::AuthenticationError)
255-
}
256-
_ => futures::future::ok(res),
257-
}
258-
},
259-
)
260-
.and_then(|mut res| {
261-
let body = mem::replace(res.body_mut(), Decoder::empty());
262-
body.concat2().map_err(|err| Error::ProtocolError {
263-
error: format!("{}", err),
264-
})
265-
})
266-
.and_then(|body| {
267-
if let Ok(utf8) = std::str::from_utf8(&body) {
268-
let s = utf8.to_owned();
269-
270-
// todo: improve error parsing without serde
271-
if s.contains("\"error\"") {
272-
return futures::future::err(Error::DatabaseError {
273-
error: format!("influxdb error: \"{}\"", s),
274-
});
275-
}
276-
277-
return futures::future::ok(s);
278-
}
279-
280-
futures::future::err(Error::DeserializationError {
281-
error: "response could not be converted to UTF-8".to_string(),
282-
})
283-
}),
284-
)
226+
227+
let res = client
228+
.send()
229+
.map_err(|err| Error::ConnectionError { error: err })
230+
.await?;
231+
232+
match res.status() {
233+
StatusCode::UNAUTHORIZED => return Err(Error::AuthorizationError),
234+
StatusCode::FORBIDDEN => return Err(Error::AuthenticationError),
235+
_ => {}
236+
}
237+
238+
let s = res.text().await.map_err(|_| Error::DeserializationError {
239+
error: "response could not be converted to UTF-8".to_string(),
240+
})?;
241+
242+
// todo: improve error parsing without serde
243+
if s.contains("\"error\"") {
244+
return Err(Error::DatabaseError {
245+
error: format!("influxdb error: \"{}\"", s),
246+
});
247+
}
248+
249+
Ok(s)
285250
}
286251
}
287252

0 commit comments

Comments
 (0)