use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::rest::{ResourceOperation, ResourcePath, RestResource};
use crate::HttpMethod;
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct Page {
#[serde(skip_serializing)]
pub id: Option<u64>,
#[serde(skip_serializing)]
pub shop_id: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub handle: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body_html: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub author: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub template_suffix: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub published_at: Option<DateTime<Utc>>,
#[serde(skip_serializing)]
pub created_at: Option<DateTime<Utc>>,
#[serde(skip_serializing)]
pub updated_at: Option<DateTime<Utc>>,
#[serde(skip_serializing)]
pub admin_graphql_api_id: Option<String>,
}
impl RestResource for Page {
type Id = u64;
type FindParams = PageFindParams;
type AllParams = PageListParams;
type CountParams = PageCountParams;
const NAME: &'static str = "Page";
const PLURAL: &'static str = "pages";
const PATHS: &'static [ResourcePath] = &[
ResourcePath::new(
HttpMethod::Get,
ResourceOperation::Find,
&["id"],
"pages/{id}",
),
ResourcePath::new(HttpMethod::Get, ResourceOperation::All, &[], "pages"),
ResourcePath::new(
HttpMethod::Get,
ResourceOperation::Count,
&[],
"pages/count",
),
ResourcePath::new(HttpMethod::Post, ResourceOperation::Create, &[], "pages"),
ResourcePath::new(
HttpMethod::Put,
ResourceOperation::Update,
&["id"],
"pages/{id}",
),
ResourcePath::new(
HttpMethod::Delete,
ResourceOperation::Delete,
&["id"],
"pages/{id}",
),
];
fn get_id(&self) -> Option<Self::Id> {
self.id
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct PageFindParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub fields: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct PageListParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub handle: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub published_status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_at_min: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_at_max: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_at_min: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_at_max: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub published_at_min: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub published_at_max: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub since_id: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub page_info: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fields: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct PageCountParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub published_status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_at_min: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_at_max: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_at_min: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_at_max: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub published_at_min: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub published_at_max: Option<DateTime<Utc>>,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rest::{get_path, ResourceOperation};
#[test]
fn test_page_struct_serialization() {
let page = Page {
id: Some(12345),
shop_id: Some(67890),
title: Some("About Us".to_string()),
handle: Some("about-us".to_string()),
body_html: Some("<p>Welcome to our store!</p>".to_string()),
author: Some("Store Admin".to_string()),
template_suffix: Some("contact".to_string()),
published_at: Some(
DateTime::parse_from_rfc3339("2024-01-15T10:30:00Z")
.unwrap()
.with_timezone(&Utc),
),
created_at: Some(
DateTime::parse_from_rfc3339("2024-01-10T08:00:00Z")
.unwrap()
.with_timezone(&Utc),
),
updated_at: Some(
DateTime::parse_from_rfc3339("2024-06-20T15:45:00Z")
.unwrap()
.with_timezone(&Utc),
),
admin_graphql_api_id: Some("gid://shopify/OnlineStorePage/12345".to_string()),
};
let json = serde_json::to_string(&page).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["title"], "About Us");
assert_eq!(parsed["handle"], "about-us");
assert_eq!(parsed["body_html"], "<p>Welcome to our store!</p>");
assert_eq!(parsed["author"], "Store Admin");
assert_eq!(parsed["template_suffix"], "contact");
assert!(parsed["published_at"].as_str().is_some());
assert!(parsed.get("id").is_none());
assert!(parsed.get("shop_id").is_none());
assert!(parsed.get("created_at").is_none());
assert!(parsed.get("updated_at").is_none());
assert!(parsed.get("admin_graphql_api_id").is_none());
}
#[test]
fn test_page_deserialization_from_api_response() {
let json = r#"{
"id": 131092082,
"shop_id": 548380009,
"title": "About Us",
"handle": "about-us",
"body_html": "<p>Welcome to our store!</p>",
"author": "Store Admin",
"template_suffix": null,
"published_at": "2024-01-15T10:30:00Z",
"created_at": "2024-01-10T08:00:00Z",
"updated_at": "2024-06-20T15:45:00Z",
"admin_graphql_api_id": "gid://shopify/OnlineStorePage/131092082"
}"#;
let page: Page = serde_json::from_str(json).unwrap();
assert_eq!(page.id, Some(131092082));
assert_eq!(page.shop_id, Some(548380009));
assert_eq!(page.title, Some("About Us".to_string()));
assert_eq!(page.handle, Some("about-us".to_string()));
assert_eq!(
page.body_html,
Some("<p>Welcome to our store!</p>".to_string())
);
assert_eq!(page.author, Some("Store Admin".to_string()));
assert!(page.template_suffix.is_none());
assert!(page.published_at.is_some());
assert!(page.created_at.is_some());
assert!(page.updated_at.is_some());
assert_eq!(
page.admin_graphql_api_id,
Some("gid://shopify/OnlineStorePage/131092082".to_string())
);
}
#[test]
fn test_page_list_params_serialization() {
let params = PageListParams {
title: Some("Contact".to_string()),
handle: Some("contact".to_string()),
published_status: Some("published".to_string()),
limit: Some(50),
since_id: Some(100),
..Default::default()
};
let json = serde_json::to_value(¶ms).unwrap();
assert_eq!(json["title"], "Contact");
assert_eq!(json["handle"], "contact");
assert_eq!(json["published_status"], "published");
assert_eq!(json["limit"], 50);
assert_eq!(json["since_id"], 100);
assert!(json.get("created_at_min").is_none());
assert!(json.get("page_info").is_none());
}
#[test]
fn test_page_count_params_serialization() {
let params = PageCountParams {
title: Some("About".to_string()),
published_status: Some("any".to_string()),
..Default::default()
};
let json = serde_json::to_value(¶ms).unwrap();
assert_eq!(json["title"], "About");
assert_eq!(json["published_status"], "any");
let empty_params = PageCountParams::default();
let empty_json = serde_json::to_value(&empty_params).unwrap();
assert_eq!(empty_json, serde_json::json!({}));
}
#[test]
fn test_page_path_constants_are_correct() {
let find_path = get_path(Page::PATHS, ResourceOperation::Find, &["id"]);
assert!(find_path.is_some());
assert_eq!(find_path.unwrap().template, "pages/{id}");
assert_eq!(find_path.unwrap().http_method, HttpMethod::Get);
let all_path = get_path(Page::PATHS, ResourceOperation::All, &[]);
assert!(all_path.is_some());
assert_eq!(all_path.unwrap().template, "pages");
assert_eq!(all_path.unwrap().http_method, HttpMethod::Get);
let count_path = get_path(Page::PATHS, ResourceOperation::Count, &[]);
assert!(count_path.is_some());
assert_eq!(count_path.unwrap().template, "pages/count");
assert_eq!(count_path.unwrap().http_method, HttpMethod::Get);
let create_path = get_path(Page::PATHS, ResourceOperation::Create, &[]);
assert!(create_path.is_some());
assert_eq!(create_path.unwrap().template, "pages");
assert_eq!(create_path.unwrap().http_method, HttpMethod::Post);
let update_path = get_path(Page::PATHS, ResourceOperation::Update, &["id"]);
assert!(update_path.is_some());
assert_eq!(update_path.unwrap().template, "pages/{id}");
assert_eq!(update_path.unwrap().http_method, HttpMethod::Put);
let delete_path = get_path(Page::PATHS, ResourceOperation::Delete, &["id"]);
assert!(delete_path.is_some());
assert_eq!(delete_path.unwrap().template, "pages/{id}");
assert_eq!(delete_path.unwrap().http_method, HttpMethod::Delete);
assert_eq!(Page::NAME, "Page");
assert_eq!(Page::PLURAL, "pages");
}
#[test]
fn test_page_get_id_returns_correct_value() {
let page_with_id = Page {
id: Some(123456789),
title: Some("Test Page".to_string()),
..Default::default()
};
assert_eq!(page_with_id.get_id(), Some(123456789));
let page_without_id = Page {
id: None,
title: Some("New Page".to_string()),
..Default::default()
};
assert_eq!(page_without_id.get_id(), None);
}
#[test]
fn test_page_published_at_for_scheduling() {
let future_date = DateTime::parse_from_rfc3339("2025-12-31T23:59:59Z")
.unwrap()
.with_timezone(&Utc);
let page = Page {
title: Some("Upcoming Sale".to_string()),
body_html: Some("<p>Coming soon!</p>".to_string()),
published_at: Some(future_date),
..Default::default()
};
let json = serde_json::to_value(&page).unwrap();
assert!(json["published_at"].as_str().is_some());
assert!(json["published_at"]
.as_str()
.unwrap()
.contains("2025-12-31"));
}
}