use std::collections::HashMap;
use crate::clients::graphql::GraphqlError;
use crate::clients::storefront::storefront_http::StorefrontHttpClient;
use crate::clients::storefront::StorefrontToken;
use crate::clients::{DataType, HttpMethod, HttpRequest, HttpResponse};
use crate::config::{ApiVersion, ShopDomain, ShopifyConfig};
#[derive(Debug)]
pub struct StorefrontClient {
http_client: StorefrontHttpClient,
api_version: ApiVersion,
}
const _: fn() = || {
const fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<StorefrontClient>();
};
impl StorefrontClient {
#[must_use]
#[allow(clippy::needless_pass_by_value)] pub fn new(
shop: &ShopDomain,
token: Option<StorefrontToken>,
config: Option<&ShopifyConfig>,
) -> Self {
let api_version = config.map_or_else(ApiVersion::latest, |c| c.api_version().clone());
Self::create_client(shop, token.as_ref(), config, api_version)
}
#[must_use]
#[allow(clippy::needless_pass_by_value)] pub fn with_version(
shop: &ShopDomain,
token: Option<StorefrontToken>,
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!(
"Storefront client has a redundant API version override to the default {}",
cfg_version
);
} else {
tracing::debug!(
"Storefront client overriding default API version {} with {}",
cfg_version,
version
);
}
}
Self::create_client(shop, token.as_ref(), config, version)
}
fn create_client(
shop: &ShopDomain,
token: Option<&StorefrontToken>,
config: Option<&ShopifyConfig>,
api_version: ApiVersion,
) -> Self {
let http_client = StorefrontHttpClient::new(shop, token, config, &api_version);
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::*;
#[test]
fn test_storefront_client_new_creates_client_with_latest_version() {
let shop = ShopDomain::new("test-shop").unwrap();
let client = StorefrontClient::new(&shop, None, None);
assert_eq!(client.api_version(), &ApiVersion::latest());
}
#[test]
fn test_storefront_client_new_with_public_token() {
let shop = ShopDomain::new("test-shop").unwrap();
let token = StorefrontToken::Public("test-token".to_string());
let client = StorefrontClient::new(&shop, Some(token), None);
assert_eq!(client.api_version(), &ApiVersion::latest());
}
#[test]
fn test_storefront_client_new_with_private_token() {
let shop = ShopDomain::new("test-shop").unwrap();
let token = StorefrontToken::Private("test-token".to_string());
let client = StorefrontClient::new(&shop, Some(token), None);
assert_eq!(client.api_version(), &ApiVersion::latest());
}
#[test]
fn test_storefront_client_new_tokenless() {
let shop = ShopDomain::new("test-shop").unwrap();
let client = StorefrontClient::new(&shop, None, None);
assert_eq!(client.api_version(), &ApiVersion::latest());
}
#[test]
fn test_storefront_client_with_version_overrides_config() {
let shop = ShopDomain::new("test-shop").unwrap();
let client = StorefrontClient::with_version(&shop, None, None, ApiVersion::V2024_10);
assert_eq!(client.api_version(), &ApiVersion::V2024_10);
}
#[test]
fn test_storefront_client_is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<StorefrontClient>();
}
#[test]
fn test_storefront_client_constructor_is_infallible() {
let shop = ShopDomain::new("test-shop").unwrap();
let _client: StorefrontClient = StorefrontClient::new(&shop, None, None);
}
#[test]
fn test_storefront_client_with_config_uses_config_version() {
use crate::config::{ApiKey, ApiSecretKey};
let shop = ShopDomain::new("test-shop").unwrap();
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 = StorefrontClient::new(&shop, None, Some(&config));
assert_eq!(client.api_version(), &ApiVersion::V2024_10);
}
#[test]
fn test_storefront_client_with_version_logs_debug_when_overriding() {
use crate::config::{ApiKey, ApiSecretKey};
let shop = ShopDomain::new("test-shop").unwrap();
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 =
StorefrontClient::with_version(&shop, None, Some(&config), ApiVersion::V2024_07);
assert_eq!(client.api_version(), &ApiVersion::V2024_07);
}
#[test]
fn test_api_version_accessor() {
let shop = ShopDomain::new("test-shop").unwrap();
let client_2024_10 =
StorefrontClient::with_version(&shop, None, None, ApiVersion::V2024_10);
let client_2024_07 =
StorefrontClient::with_version(&shop, None, None, ApiVersion::V2024_07);
let client_latest = StorefrontClient::new(&shop, None, None);
assert_eq!(client_2024_10.api_version(), &ApiVersion::V2024_10);
assert_eq!(client_2024_07.api_version(), &ApiVersion::V2024_07);
assert_eq!(client_latest.api_version(), &ApiVersion::latest());
}
}