Repository: seanmonstar/reqwest (language: rust)

Issue:
Allow to set the user-agent on wasm target
Currently `reqwest` provide the method `ClientBuilder::user_agent(..)` to set the `User-Agent` header.

But this method does not exist when targeting `wasm32-unknown-unknown`.

## Steps to reproduce

I've the following `src/lib.rs`:

```rust
use reqwest::ClientBuilder;

pub async fn test() {
    let client = ClientBuilder::default()
        .user_agent("FooBar/1.2.3")
        .build()
        .unwrap();

    let _rep = client.get("https://www.google.com").send().await.unwrap();
}
```

and the following `Cargo.toml`:

```toml
[package]
name = "request-user-agent-wasm"
version = "0.1.0"
edition = "2021"

[dependencies]
reqwest = "0.11.22"
```

When I execute `cargo check --target wasm32-unknown-unknown`, I get the following error:

```
    Checking request-user-agent-wasm v0.1.0 (/tmp/request-user-agent-wasm)
error[E0599]: no method named `user_agent` found for struct `ClientBuilder` in the current scope
 --> src/lib.rs:5:10
  |
4 |       let client = ClientBuilder::default()
  |  __________________-
5 | |         .user_agent("FooBar/1.2.3")
  | |         -^^^^^^^^^^ method not found in `ClientBuilder`
  | |_________|
  | 

For more information about this error, try `rustc --explain E0599`.
error: could not compile `request-user-agent-wasm` (lib) due to previous error
```

## What I'm expecting

I would like that `reqwest::wasm::ClientBuilder` provide the method `user_agent(..)` to set the `User-Agent` header

## Current workaround

`reqwest::wasm::ClientBuilder` provide `default_headers(..)` so I can use it to set `User-Agent`

```diff
--- src/lib.rs.back	2023-10-30 15:59:03.729542547 +0100
+++ src/lib.rs	2023-10-30 16:01:03.162871429 +0100
@@ -1,11 +1,14 @@
-use reqwest::ClientBuilder;
+use reqwest::{header, ClientBuilder};
 
 pub async fn test() {
+    let headers = header::HeaderMap::from_iter([(
+        header::HeaderName::from_static("User-Agent"),
+        header::HeaderValue::from_static("FooBar/1.2.3"),
+    )]);
     let client = ClientBuilder::default()
-        .user_agent("FooBar/1.2.3")
+        .default_headers(headers)
         .build()
         .unwrap();
 
     let _rep = client.get("https://www.google.com").send().await.unwrap();
 }
-
```



Discussion / hints:
It should be a small addition to make this available. Would you like to submit a PR?
Yes, I'm already working on a fix

Fix patch:
diff --git a/src/wasm/client.rs b/src/wasm/client.rs
index cfe97bf03..9f22a896b 100644
--- a/src/wasm/client.rs
+++ b/src/wasm/client.rs
@@ -1,5 +1,7 @@
-use http::{HeaderMap, Method};
+use http::header::USER_AGENT;
+use http::{HeaderMap, HeaderValue, Method};
 use js_sys::{Promise, JSON};
+use std::convert::TryInto;
 use std::{fmt, future::Future, sync::Arc};
 use url::Url;
 use wasm_bindgen::prelude::{wasm_bindgen, UnwrapThrowExt as _};
@@ -266,12 +268,33 @@ impl ClientBuilder {
 
     /// Returns a 'Client' that uses this ClientBuilder configuration
     pub fn build(mut self) -> Result<Client, crate::Error> {
+        if let Some(err) = self.config.error {
+            return Err(err);
+        }
+
         let config = std::mem::take(&mut self.config);
         Ok(Client {
             config: Arc::new(config),
         })
     }
 
+    /// Sets the `User-Agent` header to be used by this client.
+    pub fn user_agent<V>(mut self, value: V) -> ClientBuilder
+    where
+        V: TryInto<HeaderValue>,
+        V::Error: Into<http::Error>,
+    {
+        match value.try_into() {
+            Ok(value) => {
+                self.config.headers.insert(USER_AGENT, value);
+            }
+            Err(e) => {
+                self.config.error = Some(crate::error::builder(e.into()));
+            }
+        }
+        self
+    }
+
     /// Sets the default headers for every request
     pub fn default_headers(mut self, headers: HeaderMap) -> ClientBuilder {
         for (key, value) in headers.iter() {
@@ -287,15 +310,17 @@ impl Default for ClientBuilder {
     }
 }
 
-#[derive(Clone, Debug)]
+#[derive(Debug)]
 struct Config {
     headers: HeaderMap,
+    error: Option<crate::Error>,
 }
 
 impl Default for Config {
     fn default() -> Config {
         Config {
             headers: HeaderMap::new(),
+            error: None,
         }
     }
 }
@@ -376,4 +401,47 @@ mod tests {
             "request headers don't change client defaults"
         );
     }
+
+    #[wasm_bindgen_test]
+    fn user_agent_header() {
+        use crate::header::USER_AGENT;
+
+        let client = crate::Client::builder()
+            .user_agent("FooBar/1.2.3")
+            .build()
+            .expect("client");
+
+        let mut req = client
+            .get("https://www.example.com")
+            .build()
+            .expect("request");
+
+        // Merge the client headers with the request's one.
+        client.merge_headers(&mut req);
+        let headers1 = req.headers();
+
+        // Confirm that we have the `User-Agent` header set
+        assert_eq!(
+            headers1.get(USER_AGENT).unwrap(),
+            "FooBar/1.2.3",
+            "The user-agent header was not set: {req:#?}"
+        );
+
+        // Now we try to overwrite the `User-Agent` value
+
+        let mut req2 = client
+            .get("https://www.example.com")
+            .header(USER_AGENT, "Another-User-Agent/42")
+            .build()
+            .expect("request 2");
+
+        client.merge_headers(&mut req2);
+        let headers2 = req2.headers();
+
+        assert_eq!(
+            headers2.get(USER_AGENT).expect("headers2 user agent"),
+            "Another-User-Agent/42",
+            "Was not able to overwrite the User-Agent value on the request-builder"
+        );
+    }
 }


Test patch: