Skip to main content

shopify_client/common/
http.rs

1use crate::common::types::{APIError, RequestCallbacks};
2use reqwest::header::{HeaderMap, HeaderValue, CONTENT_TYPE};
3
4#[derive(serde::Serialize)]
5struct GraphQLRequest {
6    query: String,
7    variables: serde_json::Value,
8}
9
10#[derive(serde::Deserialize)]
11struct GraphQLResponse<T> {
12    data: Option<T>,
13    errors: Option<Vec<GraphQLError>>,
14}
15
16#[derive(serde::Deserialize, Debug)]
17struct GraphQLError {
18    message: String,
19}
20
21pub async fn execute_graphql<T: serde::de::DeserializeOwned>(
22    shop_url: &String,
23    version: &String,
24    access_token: &String,
25    callbacks: &RequestCallbacks,
26    query: String,
27    variables: serde_json::Value,
28) -> Result<T, APIError> {
29    let endpoint = format!("{}/admin/api/{}/graphql.json", shop_url, version);
30
31    let request_body = GraphQLRequest { query, variables };
32
33    let body_str = serde_json::to_string(&request_body).unwrap_or_default();
34
35    let mut callback_headers = HeaderMap::new();
36    callback_headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
37
38    callbacks.call_before(&endpoint, Some(&body_str), &callback_headers);
39
40    let client = reqwest::Client::new();
41    let response = client
42        .post(&endpoint)
43        .header("X-Shopify-Access-Token", access_token)
44        .header("Content-Type", "application/json")
45        .json(&request_body)
46        .send()
47        .await;
48
49    match response {
50        Ok(resp) => {
51            let response_headers = resp.headers().clone();
52            let response_text = match resp.text().await {
53                Ok(text) => text,
54                Err(e) => {
55                    let error_msg = format!("<failed to read response body: {}>", e);
56                    callbacks.call_after(&endpoint, &error_msg, &response_headers);
57                    return Err(APIError::FailedToParse);
58                }
59            };
60
61            callbacks.call_after(&endpoint, &response_text, &response_headers);
62
63            let graphql_response = serde_json::from_str::<GraphQLResponse<T>>(&response_text)
64                .map_err(|_| APIError::FailedToParse)?;
65
66            if let Some(errors) = graphql_response.errors {
67                let error_messages: Vec<String> =
68                    errors.iter().map(|e| e.message.clone()).collect();
69                return Err(APIError::ServerError {
70                    errors: error_messages.join(", "),
71                });
72            }
73
74            graphql_response.data.ok_or(APIError::FailedToParse)
75        }
76        Err(e) => {
77            let error_msg = format!("<network error: {}>", e);
78            callbacks.call_after(&endpoint, &error_msg, &HeaderMap::new());
79            Err(APIError::NetworkError)
80        }
81    }
82}