use std::collections::HashMap;
use crate::auth::Session;
use crate::clients::graphql::GraphqlError;
use crate::clients::{DataType, HttpClient, HttpMethod, HttpRequest, HttpResponse};
use crate::config::{ApiVersion, ShopifyConfig};
#[derive(Debug)]
pub struct GraphqlClient {
http_client: HttpClient,
api_version: ApiVersion,
}
const _: fn() = || {
const fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<GraphqlClient>();
};
impl GraphqlClient {
#[must_use]
pub fn new(session: &Session, config: Option<&ShopifyConfig>) -> Self {
let api_version = config.map_or_else(ApiVersion::latest, |c| c.api_version().clone());
Self::create_client(session, config, api_version)
}
#[must_use]
pub fn with_version(
session: &Session,
config: Option<&ShopifyConfig>,
version: ApiVersion,
) -> Self {
let config_version = config.map(|c| c.api_version().clone());
if let Some(ref cfg_version) = config_version {
if &version == cfg_version {
tracing::debug!(
"GraphQL client has a redundant API version override to the default {}",
cfg_version
);
} else {
tracing::debug!(
"GraphQL client overriding default API version {} with {}",
cfg_version,
version
);
}
}
Self::create_client(session, config, version)
}
fn create_client(
session: &Session,
config: Option<&ShopifyConfig>,
api_version: ApiVersion,
) -> Self {
let base_path = format!("/admin/api/{api_version}");
let http_client = HttpClient::new(base_path, session, config);
Self {
http_client,
api_version,
}
}
#[must_use]
pub const fn api_version(&self) -> &ApiVersion {
&self.api_version
}
pub async fn query(
&self,
query: &str,
variables: Option<serde_json::Value>,
headers: Option<HashMap<String, String>>,
tries: Option<u32>,
) -> Result<HttpResponse, GraphqlError> {
self.execute_query(query, variables, headers, tries, false)
.await
}
pub async fn query_with_debug(
&self,
query: &str,
variables: Option<serde_json::Value>,
headers: Option<HashMap<String, String>>,
tries: Option<u32>,
) -> Result<HttpResponse, GraphqlError> {
self.execute_query(query, variables, headers, tries, true)
.await
}
async fn execute_query(
&self,
query: &str,
variables: Option<serde_json::Value>,
headers: Option<HashMap<String, String>>,
tries: Option<u32>,
debug: bool,
) -> Result<HttpResponse, GraphqlError> {
let body = serde_json::json!({
"query": query,
"variables": variables
});
let mut builder = HttpRequest::builder(HttpMethod::Post, "graphql.json")
.body(body)
.body_type(DataType::Json)
.tries(tries.unwrap_or(1));
if debug {
builder = builder.query_param("debug", "true");
}
if let Some(extra_headers) = headers {
builder = builder.extra_headers(extra_headers);
}
let request = builder.build().map_err(|e| GraphqlError::Http(e.into()))?;
self.http_client.request(request).await.map_err(Into::into)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::auth::AuthScopes;
use crate::config::ShopDomain;
fn create_test_session() -> Session {
Session::new(
"test-session".to_string(),
ShopDomain::new("test-shop").unwrap(),
"test-access-token".to_string(),
AuthScopes::new(),
false,
None,
)
}
#[test]
fn test_graphql_client_new_creates_client_with_latest_version() {
let session = create_test_session();
let client = GraphqlClient::new(&session, None);
assert_eq!(client.api_version(), &ApiVersion::latest());
}
#[test]
fn test_graphql_client_with_version_overrides_config() {
let session = create_test_session();
let client = GraphqlClient::with_version(&session, None, ApiVersion::V2024_10);
assert_eq!(client.api_version(), &ApiVersion::V2024_10);
}
#[test]
fn test_graphql_client_is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<GraphqlClient>();
}
#[test]
fn test_graphql_client_constructor_is_infallible() {
let session = create_test_session();
let _client: GraphqlClient = GraphqlClient::new(&session, None);
}
#[test]
fn test_graphql_client_with_config_uses_config_version() {
use crate::config::{ApiKey, ApiSecretKey};
let session = create_test_session();
let config = ShopifyConfig::builder()
.api_key(ApiKey::new("test-key").unwrap())
.api_secret_key(ApiSecretKey::new("test-secret").unwrap())
.api_version(ApiVersion::V2024_10)
.build()
.unwrap();
let client = GraphqlClient::new(&session, Some(&config));
assert_eq!(client.api_version(), &ApiVersion::V2024_10);
}
#[test]
fn test_graphql_client_with_version_logs_debug_when_overriding() {
use crate::config::{ApiKey, ApiSecretKey};
let session = create_test_session();
let config = ShopifyConfig::builder()
.api_key(ApiKey::new("test-key").unwrap())
.api_secret_key(ApiSecretKey::new("test-secret").unwrap())
.api_version(ApiVersion::V2024_10)
.build()
.unwrap();
let client = GraphqlClient::with_version(&session, Some(&config), ApiVersion::V2024_07);
assert_eq!(client.api_version(), &ApiVersion::V2024_07);
}
}