sui_graphql_rpc_client/
response.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use super::ClientError;
5use async_graphql::{Response, ServerError, Value};
6use reqwest::Response as ReqwestResponse;
7use reqwest::header::{HeaderMap, HeaderName};
8use serde_json::json;
9use std::{collections::BTreeMap, net::SocketAddr};
10use sui_graphql_rpc_headers::VERSION_HEADER;
11
12#[derive(Debug)]
13pub struct GraphqlResponse {
14    headers: HeaderMap,
15    remote_address: Option<SocketAddr>,
16    http_version: reqwest::Version,
17    status: reqwest::StatusCode,
18    full_response: Response,
19}
20
21impl GraphqlResponse {
22    pub async fn from_resp(resp: ReqwestResponse) -> Result<Self, ClientError> {
23        let headers = resp.headers().clone();
24        let remote_address = resp.remote_addr();
25        let http_version = resp.version();
26        let status = resp.status();
27        let full_response: Response = resp.json().await.map_err(ClientError::InnerClientError)?;
28
29        Ok(Self {
30            headers,
31            remote_address,
32            http_version,
33            status,
34            full_response,
35        })
36    }
37
38    #[allow(clippy::result_large_err)]
39    pub fn graphql_version(&self) -> Result<String, ClientError> {
40        Ok(self
41            .headers
42            .get(VERSION_HEADER.as_str())
43            .ok_or(ClientError::ServiceVersionHeaderNotFound)?
44            .to_str()
45            .map_err(|e| ClientError::ServiceVersionHeaderValueInvalidString { error: e })?
46            .to_string())
47    }
48
49    pub fn response_body(&self) -> &Response {
50        &self.full_response
51    }
52
53    pub fn response_body_json(&self) -> serde_json::Value {
54        json!(self.full_response)
55    }
56
57    pub fn response_body_json_pretty(&self) -> String {
58        serde_json::to_string_pretty(&self.full_response).unwrap()
59    }
60
61    pub fn http_status(&self) -> reqwest::StatusCode {
62        self.status
63    }
64
65    pub fn http_version(&self) -> reqwest::Version {
66        self.http_version
67    }
68
69    pub fn http_headers(&self) -> HeaderMap {
70        self.headers.clone()
71    }
72
73    /// Returns the HTTP headers without the `Date` header.
74    /// The `Date` header is removed because it is not deterministic.
75    pub fn http_headers_without_date(&self) -> HeaderMap {
76        let mut headers = self.http_headers().clone();
77        headers.remove(HeaderName::from_static("date"));
78        headers
79    }
80
81    pub fn remote_address(&self) -> Option<SocketAddr> {
82        self.remote_address
83    }
84
85    pub fn errors(&self) -> Vec<ServerError> {
86        self.full_response.errors.clone()
87    }
88
89    #[allow(clippy::result_large_err)]
90    pub fn usage(&self) -> Result<Option<BTreeMap<String, u64>>, ClientError> {
91        Ok(match self.full_response.extensions.get("usage").cloned() {
92            Some(Value::Object(obj)) => Some(
93                obj.into_iter()
94                    .map(|(k, v)| match v {
95                        Value::Number(n) => {
96                            n.as_u64().ok_or(ClientError::InvalidUsageNumber {
97                                usage_name: k.to_string(),
98                                usage_number: n,
99                            })
100                        }
101                        .map(|q| (k.to_string(), q)),
102                        _ => Err(ClientError::InvalidUsageValue {
103                            usage_name: k.to_string(),
104                            usage_value: v,
105                        }),
106                    })
107                    .collect::<Result<BTreeMap<String, u64>, ClientError>>()?,
108            ),
109            _ => None,
110        })
111    }
112}