Repository: gsquire/sendgrid-rs (language: rust)

Issue:
Async feature violates additive property
Cargo features are [required to be additive](https://doc.rust-lang.org/cargo/reference/features.html?highlight=additive#feature-unification) to function properly when a crate is used transitively multiple times in a complete program.

The `async` feature violates this property by changing the type of `SGClient::send` in an incompatible way. Would it be possible to convert the async implementation to a separate method with its own name (or maybe make a different async client type)?


Discussion / hints:
If I am understanding your issue correctly, you have a program that depends on both versions of `SGClient::send`?
It's easy for me to work around this, but yes.
Perhaps I should follow `reqwest`'s lead and revise the blocking feature such that it can include a different module to avoid conflicts.

Fix patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6cb31ff..b25de0a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -8,7 +8,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        blocking: ["blocking", "async"]
+        blocking: ["blocking"]
         tls: ["native-tls", "rustls"]
         
     steps:
@@ -26,3 +26,6 @@ jobs:
 
       - name: Test with ${{ matrix.tls }} and ${{ matrix.blocking }}
         run: cargo test --no-default-features --features ${{ matrix.tls }},${{ matrix.blocking }}
+
+      - name: Async test
+        run: cargo test
diff --git a/Cargo.toml b/Cargo.toml
index e5f2a1d..07c9db3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "sendgrid"
-version = "0.19.2"
+version = "0.20.0"
 edition = "2021"
 authors = ["Garrett Squire <github@garrettsquire.com>"]
 description = "An unofficial client library for the SendGrid API"
@@ -12,27 +12,25 @@ documentation = "https://docs.rs/sendgrid"
 readme = "README.md"
 
 [dependencies]
-data-encoding = "2.3"
+data-encoding = "2.5"
 reqwest = { version = "0.11", default-features = false, features = ["json"] }
 serde = { version = "1.0", features = ["derive"] }
 serde_json = "1.0"
 thiserror = "1.0"
-url = "2.3"
+url = "2.5"
 
 [dev-dependencies]
 tokio = { version = "1.0", features = ["full"] }
 
 [features]
-default = ["native-tls", "blocking"]
+default = ["native-tls"]
 
-async = []
 blocking = ["reqwest/blocking"]
 rustls = ["reqwest/rustls-tls"]
 native-tls = ["reqwest/default-tls"]
 
 [[example]]
 name = "v3_async"
-required-features = ["async"]
 
 [[example]]
 name = "main"
diff --git a/README.md b/README.md
index 9716a45..55f42e7 100644
--- a/README.md
+++ b/README.md
@@ -21,8 +21,8 @@ This library utilises [reqwest](https://crates.io/crates/reqwest). Follow the in
 requests to the SendGrid API.
 
 ## Features
-You can take advantage of a couple features for the crate. To enable the asynchronous send function,
-you can use the `async` flag. To enable the [rustls](https://github.com/ctz/rustls) TLS feature,
+You can take advantage of a couple features for the crate. To enable the blocking send function, you
+can use the `blocking` flag. To enable the [rustls](https://github.com/rustls/rustls) TLS feature,
 use the `rustls` flag.
 
 ## Example
diff --git a/examples/main.rs b/examples/main.rs
index 4bebf27..f45a553 100644
--- a/examples/main.rs
+++ b/examples/main.rs
@@ -27,7 +27,7 @@ fn main() {
         .add_header("x-cool".to_string(), "indeed")
         .add_x_smtpapi(&x_smtpapi);
 
-    match sg.send(mail_info) {
+    match sg.blocking_send(mail_info) {
         Err(err) => println!("Error: {}", err),
         Ok(body) => println!("Response: {:?}", body),
     };
diff --git a/examples/v3.rs b/examples/v3.rs
index fb4a07c..964b674 100644
--- a/examples/v3.rs
+++ b/examples/v3.rs
@@ -20,6 +20,6 @@ fn main() {
 
     let api_key = ::std::env::var("SG_API_KEY").unwrap();
     let sender = Sender::new(api_key);
-    let code = sender.send(&m);
+    let code = sender.blocking_send(&m);
     println!("{:?}", code);
 }
diff --git a/src/client.rs b/src/client.rs
index 7838849..b81b7bf 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -1,8 +1,7 @@
 use reqwest::header::{self, HeaderMap, HeaderValue};
 
-#[cfg(not(feature = "async"))]
-use reqwest::blocking::Response;
-#[cfg(feature = "async")]
+#[cfg(feature = "blocking")]
+use reqwest::blocking::Response as BlockingResponse;
 use reqwest::Response;
 
 use url::form_urlencoded::Serializer;
@@ -20,10 +19,9 @@ static API_URL: &str = "https://api.sendgrid.com/api/mail.send.json?";
 pub struct SGClient {
     api_key: String,
     host: String,
-    #[cfg(feature = "async")]
     client: reqwest::Client,
-    #[cfg(not(feature = "async"))]
-    client: reqwest::blocking::Client,
+    #[cfg(feature = "blocking")]
+    blocking_client: reqwest::blocking::Client,
 }
 
 // Given a form value and a key, generate the correct key.
@@ -81,19 +79,27 @@ impl SGClient {
     /// default TLS backend and do not have a default TLS backend available. If you are using the
     /// RustTLS backend, this can never panic because RustTLS is statically linked.
     pub fn new<S: Into<String>>(key: S) -> SGClient {
-        #[cfg(feature = "async")]
-        use reqwest as rq;
-        #[cfg(not(feature = "async"))]
-        use reqwest::blocking as rq;
-
-        let builder = rq::ClientBuilder::new();
+        let async_builder = reqwest::ClientBuilder::new();
         #[cfg(feature = "rustls")]
-        let builder = builder.use_rustls_tls();
-        let client = builder.build().unwrap();
+        let async_builder = async_builder.use_rustls_tls();
+        let client = async_builder.build().unwrap();
+
+        #[cfg(feature = "blocking")]
+        let blocking_client: reqwest::blocking::Client;
+
+        #[cfg(feature = "blocking")]
+        {
+            let blocking_builder = reqwest::blocking::ClientBuilder::new();
+            #[cfg(feature = "rustls")]
+            let blocking_builder = blocking_builder.use_rustls_tls();
+            blocking_client = blocking_builder.build().unwrap();
+        }
 
         SGClient {
             api_key: key.into(),
             client,
+            #[cfg(feature = "blocking")]
+            blocking_client,
             host: API_URL.to_string(),
         }
     }
@@ -126,11 +132,11 @@ impl SGClient {
     ///     Ok(())
     /// }
     /// ```
-    #[cfg(not(feature = "async"))]
-    pub fn send(&self, mail_info: Mail) -> SendgridResult<Response> {
+    #[cfg(feature = "blocking")]
+    pub fn blocking_send(&self, mail_info: Mail) -> SendgridResult<BlockingResponse> {
         let post_body = make_post_body(mail_info)?;
         let resp = self
-            .client
+            .blocking_client
             .post(&self.host)
             .headers(self.headers()?)
             .body(post_body)
@@ -167,7 +173,6 @@ impl SGClient {
     ///     Ok(())
     /// }
     /// ```
-    #[cfg(feature = "async")]
     pub async fn send(&self, mail_info: Mail<'_>) -> SendgridResult<Response> {
         let post_body = make_post_body(mail_info)?;
         let resp = self
diff --git a/src/v3.rs b/src/v3.rs
index 4668ee8..14a9d2a 100644
--- a/src/v3.rs
+++ b/src/v3.rs
@@ -8,9 +8,8 @@ use reqwest::header::{self, HeaderMap, HeaderValue, InvalidHeaderValue};
 use serde::Serialize;
 use serde_json::{to_value, value::Value, value::Value::Object, Map};
 
-#[cfg(not(feature = "async"))]
-use reqwest::blocking::{Client, Response};
-#[cfg(feature = "async")]
+#[cfg(feature = "blocking")]
+use reqwest::blocking::Response as BlockingResponse;
 use reqwest::{Client, Response};
 
 use crate::error::{RequestNotSuccessful, SendgridError, SendgridResult};
@@ -25,6 +24,8 @@ pub type SGMap = HashMap<String, String>;
 pub struct Sender {
     api_key: String,
     client: Client,
+    #[cfg(feature = "blocking")]
+    blocking_client: reqwest::blocking::Client,
     host: String,
 }
 
@@ -204,6 +205,8 @@ impl Sender {
         Sender {
             api_key,
             client: Client::new(),
+            #[cfg(feature = "blocking")]
+            blocking_client: reqwest::blocking::Client::new(),
             host: V3_API_URL.to_string(),
         }
     }
@@ -228,7 +231,6 @@ impl Sender {
         Ok(headers)
     }
 
-    #[cfg(feature = "async")]
     /// Send a V3 message and return the HTTP response or an error.
     pub async fn send(&self, mail: &Message) -> SendgridResult<Response> {
         let headers = self.get_headers()?;
@@ -248,14 +250,14 @@ impl Sender {
         Ok(resp)
     }
 
-    #[cfg(not(feature = "async"))]
+    #[cfg(feature = "blocking")]
     /// Send a V3 message and return the HTTP response or an error.
-    pub fn send(&self, mail: &Message) -> SendgridResult<Response> {
+    pub fn blocking_send(&self, mail: &Message) -> SendgridResult<BlockingResponse> {
         let headers = self.get_headers()?;
         let body = mail.gen_json();
 
         let resp = self
-            .client
+            .blocking_client
             .post(&self.host)
             .headers(headers)
             .body(body)


Test patch: