ugi 0.2.1

Runtime-agnostic Rust request client with HTTP/1.1, HTTP/2, HTTP/3, H2C, WebSocket, SSE, and gRPC support
Documentation
use futures_lite::future::block_on;
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize)]
struct GraphqlRequest<V> {
    query: &'static str,
    #[serde(rename = "operationName")]
    operation_name: &'static str,
    variables: V,
}

#[derive(Debug, Serialize)]
struct UserVariables {
    id: String,
}

#[derive(Debug, Deserialize)]
struct GraphqlResponse<T> {
    data: Option<T>,
    errors: Option<Vec<GraphqlError>>,
}

#[derive(Debug, Deserialize)]
struct GraphqlError {
    message: String,
}

#[derive(Debug, Deserialize)]
struct UserQueryData {
    user: User,
}

#[derive(Debug, Deserialize)]
struct User {
    id: String,
    name: String,
}

const USER_QUERY: &str = r#"
query UserById($id: ID!) {
  user(id: $id) {
    id
    name
  }
}
"#;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    block_on(async move {
        let client = ugi::Client::builder()
            .base_url("https://api.example.com")?
            .bearer_auth("token")?
            .prefer_http2()
            .build()?;

        let payload = GraphqlRequest {
            query: USER_QUERY,
            operation_name: "UserById",
            variables: UserVariables {
                id: "user_123".to_owned(),
            },
        };

        let response: GraphqlResponse<UserQueryData> = client
            .post("/graphql")
            .header(
                "accept",
                "application/graphql-response+json, application/json",
            )?
            .json(&payload)?
            .await?
            .json()
            .await?;

        if let Some(errors) = response.errors {
            return Err(format!("graphql error: {}", errors[0].message).into());
        }

        let data = response
            .data
            .ok_or_else(|| "graphql response missing data".to_owned())?;
        println!("user {} {}", data.user.id, data.user.name);
        Ok(())
    })
}