shopify_client/common/
http.rs1use 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}