Converting between two HeaderMap

Hello,
I'm rather new to rust, doing my first steps.

I'm using both axum and reqwest to drive my code.
I'm using these versions:

axum = "0.7.4"
reqwest = { version = "0.11.24", features = ["native-tls-vendored", "json", "serde_json"] }

There is a point in my code where the headers I return are the headers that I received from a request. Here is the code snippet:

pub async fn get_next_invocation(
    State(state): State<AppState>,
) -> (
    axum::http::StatusCode,
    axum::http::HeaderMap,
    std::string::String,
) {
...
let resp = reqwest::get(format!(
        "http://{}/address",
        state.runtime_api_address
    ))
    .await
    .unwrap();
...
 let mut headers = convert_headers_from_reqwest(&resp.headers());
...
(status, headers, data)

Due to version mismatch in the http crate that are used both by axum and reqwest, HeaderMap is considered a different type, therefore the solution I found was to convert between the two types, hence the convert_headers_from_reqwest method.

Does it make sense?

Assuming it does make sense, is there any easy way to convert between the two types or do i have to iterate over the first type and clone the values?

Thank you

Yes, until reqwest updates to http v1, converting one type to the other is your best option. Downgrading axum would be another, but that is probably not what you want.

Unfortunately you can't take ownership of the headers from a Response, so you'd have to clone the values.

Thank you for answer @jofas .
Is there any idiomatic rust code for such conversations? It feels that things like that can happen quite a lot in rust.

Given that you are converting from one foreign type to another, you can't use From for the conversion. I'd define a trait and implement that for reqwest::Response so I can write let mut headers = resp.http1_headers() rather than a convert_headers_from_reqwest function but that's a matter of taste. I maintain a crate where I provide the conversion between two foreign response types (awc client response to a actix-web http response). It's a rather niche use-case though, it really doesn't happen too often I think.

Got it.
I use something like this -

fn convert_headers_from_reqwest(
    reqwest_headers: &reqwest::header::HeaderMap,
) -> axum::http::HeaderMap {
    let mut axum_headers = axum::http::HeaderMap::new();
    for (name, value) in reqwest_headers.iter() {
        let name = axum::http::header::HeaderName::from_bytes(name.as_str().as_bytes()).unwrap();
        axum_headers.append(name, value.to_str().unwrap().parse().unwrap());
    }
    axum_headers
}

It passes the compiler, but it looks ugly :slight_smile:
Do you have any improvements to suggest for the code above?

Maybe you like this better? (I haven't tested it, could be wrong)

use std::str::FromStr;

fn convert_headers_from_reqwest(
    reqwest_headers: &reqwest::header::HeaderMap,
) -> axum::http::HeaderMap {
    let mut axum_headers = axum::http::HeaderMap::new();
    for (name, value) in reqwest_headers {
        let name = axum::http::header::HeaderName::from_str(name.as_str()).unwrap();
        axum_headers.append(name, value.as_bytes().try_into().unwrap());
    }
    axum_headers
}

Great.
How did you know it implements from_str? I didn't find it in the docs
And why value.as_bytes().try_into().unwrap() and not value.to_str().unwrap().parse().unwrap() - performance consideration ?

I found it here.

Yes, calling HeaderValue::to_str will check if every character is valid ASCII. Then you parse it (which is calling FromStr::from_str under the hood), which does the same check again. With try_into you only check for valid ASCII once rather than twice.