sui_graphql_rpc_client/
response.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use super::ClientError;
use async_graphql::{Response, ServerError, Value};
use reqwest::header::{HeaderMap, HeaderName};
use reqwest::Response as ReqwestResponse;
use serde_json::json;
use std::{collections::BTreeMap, net::SocketAddr};
use sui_graphql_rpc_headers::VERSION_HEADER;

#[derive(Debug)]
pub struct GraphqlResponse {
    headers: HeaderMap,
    remote_address: Option<SocketAddr>,
    http_version: reqwest::Version,
    status: reqwest::StatusCode,
    full_response: Response,
}

impl GraphqlResponse {
    pub async fn from_resp(resp: ReqwestResponse) -> Result<Self, ClientError> {
        let headers = resp.headers().clone();
        let remote_address = resp.remote_addr();
        let http_version = resp.version();
        let status = resp.status();
        let full_response: Response = resp.json().await.map_err(ClientError::InnerClientError)?;

        Ok(Self {
            headers,
            remote_address,
            http_version,
            status,
            full_response,
        })
    }

    #[allow(clippy::result_large_err)]
    pub fn graphql_version(&self) -> Result<String, ClientError> {
        Ok(self
            .headers
            .get(VERSION_HEADER.as_str())
            .ok_or(ClientError::ServiceVersionHeaderNotFound)?
            .to_str()
            .map_err(|e| ClientError::ServiceVersionHeaderValueInvalidString { error: e })?
            .to_string())
    }

    pub fn response_body(&self) -> &Response {
        &self.full_response
    }

    pub fn response_body_json(&self) -> serde_json::Value {
        json!(self.full_response)
    }

    pub fn response_body_json_pretty(&self) -> String {
        serde_json::to_string_pretty(&self.full_response).unwrap()
    }

    pub fn http_status(&self) -> reqwest::StatusCode {
        self.status
    }

    pub fn http_version(&self) -> reqwest::Version {
        self.http_version
    }

    pub fn http_headers(&self) -> HeaderMap {
        self.headers.clone()
    }

    /// Returns the HTTP headers without the `Date` header.
    /// The `Date` header is removed because it is not deterministic.
    pub fn http_headers_without_date(&self) -> HeaderMap {
        let mut headers = self.http_headers().clone();
        headers.remove(HeaderName::from_static("date"));
        headers
    }

    pub fn remote_address(&self) -> Option<SocketAddr> {
        self.remote_address
    }

    pub fn errors(&self) -> Vec<ServerError> {
        self.full_response.errors.clone()
    }

    #[allow(clippy::result_large_err)]
    pub fn usage(&self) -> Result<Option<BTreeMap<String, u64>>, ClientError> {
        Ok(match self.full_response.extensions.get("usage").cloned() {
            Some(Value::Object(obj)) => Some(
                obj.into_iter()
                    .map(|(k, v)| match v {
                        Value::Number(n) => {
                            n.as_u64().ok_or(ClientError::InvalidUsageNumber {
                                usage_name: k.to_string(),
                                usage_number: n,
                            })
                        }
                        .map(|q| (k.to_string(), q)),
                        _ => Err(ClientError::InvalidUsageValue {
                            usage_name: k.to_string(),
                            usage_value: v,
                        }),
                    })
                    .collect::<Result<BTreeMap<String, u64>, ClientError>>()?,
            ),
            _ => None,
        })
    }
}