1use chrono::{DateTime, Utc};
39use serde::{Deserialize, Serialize};
40
41use crate::rest::{ResourceOperation, ResourcePath, RestResource};
42use crate::HttpMethod;
43
44#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
66pub struct Page {
67 #[serde(skip_serializing)]
70 pub id: Option<u64>,
71
72 #[serde(skip_serializing)]
75 pub shop_id: Option<u64>,
76
77 #[serde(skip_serializing_if = "Option::is_none")]
79 pub title: Option<String>,
80
81 #[serde(skip_serializing_if = "Option::is_none")]
84 pub handle: Option<String>,
85
86 #[serde(skip_serializing_if = "Option::is_none")]
88 pub body_html: Option<String>,
89
90 #[serde(skip_serializing_if = "Option::is_none")]
92 pub author: Option<String>,
93
94 #[serde(skip_serializing_if = "Option::is_none")]
98 pub template_suffix: Option<String>,
99
100 #[serde(skip_serializing_if = "Option::is_none")]
104 pub published_at: Option<DateTime<Utc>>,
105
106 #[serde(skip_serializing)]
109 pub created_at: Option<DateTime<Utc>>,
110
111 #[serde(skip_serializing)]
114 pub updated_at: Option<DateTime<Utc>>,
115
116 #[serde(skip_serializing)]
119 pub admin_graphql_api_id: Option<String>,
120}
121
122impl RestResource for Page {
123 type Id = u64;
124 type FindParams = PageFindParams;
125 type AllParams = PageListParams;
126 type CountParams = PageCountParams;
127
128 const NAME: &'static str = "Page";
129 const PLURAL: &'static str = "pages";
130
131 const PATHS: &'static [ResourcePath] = &[
132 ResourcePath::new(
133 HttpMethod::Get,
134 ResourceOperation::Find,
135 &["id"],
136 "pages/{id}",
137 ),
138 ResourcePath::new(HttpMethod::Get, ResourceOperation::All, &[], "pages"),
139 ResourcePath::new(
140 HttpMethod::Get,
141 ResourceOperation::Count,
142 &[],
143 "pages/count",
144 ),
145 ResourcePath::new(HttpMethod::Post, ResourceOperation::Create, &[], "pages"),
146 ResourcePath::new(
147 HttpMethod::Put,
148 ResourceOperation::Update,
149 &["id"],
150 "pages/{id}",
151 ),
152 ResourcePath::new(
153 HttpMethod::Delete,
154 ResourceOperation::Delete,
155 &["id"],
156 "pages/{id}",
157 ),
158 ];
159
160 fn get_id(&self) -> Option<Self::Id> {
161 self.id
162 }
163}
164
165#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
167pub struct PageFindParams {
168 #[serde(skip_serializing_if = "Option::is_none")]
170 pub fields: Option<String>,
171}
172
173#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
175pub struct PageListParams {
176 #[serde(skip_serializing_if = "Option::is_none")]
178 pub title: Option<String>,
179
180 #[serde(skip_serializing_if = "Option::is_none")]
182 pub handle: Option<String>,
183
184 #[serde(skip_serializing_if = "Option::is_none")]
187 pub published_status: Option<String>,
188
189 #[serde(skip_serializing_if = "Option::is_none")]
191 pub created_at_min: Option<DateTime<Utc>>,
192
193 #[serde(skip_serializing_if = "Option::is_none")]
195 pub created_at_max: Option<DateTime<Utc>>,
196
197 #[serde(skip_serializing_if = "Option::is_none")]
199 pub updated_at_min: Option<DateTime<Utc>>,
200
201 #[serde(skip_serializing_if = "Option::is_none")]
203 pub updated_at_max: Option<DateTime<Utc>>,
204
205 #[serde(skip_serializing_if = "Option::is_none")]
207 pub published_at_min: Option<DateTime<Utc>>,
208
209 #[serde(skip_serializing_if = "Option::is_none")]
211 pub published_at_max: Option<DateTime<Utc>>,
212
213 #[serde(skip_serializing_if = "Option::is_none")]
215 pub limit: Option<u32>,
216
217 #[serde(skip_serializing_if = "Option::is_none")]
219 pub since_id: Option<u64>,
220
221 #[serde(skip_serializing_if = "Option::is_none")]
223 pub page_info: Option<String>,
224
225 #[serde(skip_serializing_if = "Option::is_none")]
227 pub fields: Option<String>,
228}
229
230#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
232pub struct PageCountParams {
233 #[serde(skip_serializing_if = "Option::is_none")]
235 pub title: Option<String>,
236
237 #[serde(skip_serializing_if = "Option::is_none")]
240 pub published_status: Option<String>,
241
242 #[serde(skip_serializing_if = "Option::is_none")]
244 pub created_at_min: Option<DateTime<Utc>>,
245
246 #[serde(skip_serializing_if = "Option::is_none")]
248 pub created_at_max: Option<DateTime<Utc>>,
249
250 #[serde(skip_serializing_if = "Option::is_none")]
252 pub updated_at_min: Option<DateTime<Utc>>,
253
254 #[serde(skip_serializing_if = "Option::is_none")]
256 pub updated_at_max: Option<DateTime<Utc>>,
257
258 #[serde(skip_serializing_if = "Option::is_none")]
260 pub published_at_min: Option<DateTime<Utc>>,
261
262 #[serde(skip_serializing_if = "Option::is_none")]
264 pub published_at_max: Option<DateTime<Utc>>,
265}
266
267#[cfg(test)]
268mod tests {
269 use super::*;
270 use crate::rest::{get_path, ResourceOperation};
271
272 #[test]
273 fn test_page_struct_serialization() {
274 let page = Page {
275 id: Some(12345),
276 shop_id: Some(67890),
277 title: Some("About Us".to_string()),
278 handle: Some("about-us".to_string()),
279 body_html: Some("<p>Welcome to our store!</p>".to_string()),
280 author: Some("Store Admin".to_string()),
281 template_suffix: Some("contact".to_string()),
282 published_at: Some(
283 DateTime::parse_from_rfc3339("2024-01-15T10:30:00Z")
284 .unwrap()
285 .with_timezone(&Utc),
286 ),
287 created_at: Some(
288 DateTime::parse_from_rfc3339("2024-01-10T08:00:00Z")
289 .unwrap()
290 .with_timezone(&Utc),
291 ),
292 updated_at: Some(
293 DateTime::parse_from_rfc3339("2024-06-20T15:45:00Z")
294 .unwrap()
295 .with_timezone(&Utc),
296 ),
297 admin_graphql_api_id: Some("gid://shopify/OnlineStorePage/12345".to_string()),
298 };
299
300 let json = serde_json::to_string(&page).unwrap();
301 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
302
303 assert_eq!(parsed["title"], "About Us");
305 assert_eq!(parsed["handle"], "about-us");
306 assert_eq!(parsed["body_html"], "<p>Welcome to our store!</p>");
307 assert_eq!(parsed["author"], "Store Admin");
308 assert_eq!(parsed["template_suffix"], "contact");
309 assert!(parsed["published_at"].as_str().is_some());
310
311 assert!(parsed.get("id").is_none());
313 assert!(parsed.get("shop_id").is_none());
314 assert!(parsed.get("created_at").is_none());
315 assert!(parsed.get("updated_at").is_none());
316 assert!(parsed.get("admin_graphql_api_id").is_none());
317 }
318
319 #[test]
320 fn test_page_deserialization_from_api_response() {
321 let json = r#"{
322 "id": 131092082,
323 "shop_id": 548380009,
324 "title": "About Us",
325 "handle": "about-us",
326 "body_html": "<p>Welcome to our store!</p>",
327 "author": "Store Admin",
328 "template_suffix": null,
329 "published_at": "2024-01-15T10:30:00Z",
330 "created_at": "2024-01-10T08:00:00Z",
331 "updated_at": "2024-06-20T15:45:00Z",
332 "admin_graphql_api_id": "gid://shopify/OnlineStorePage/131092082"
333 }"#;
334
335 let page: Page = serde_json::from_str(json).unwrap();
336
337 assert_eq!(page.id, Some(131092082));
338 assert_eq!(page.shop_id, Some(548380009));
339 assert_eq!(page.title, Some("About Us".to_string()));
340 assert_eq!(page.handle, Some("about-us".to_string()));
341 assert_eq!(
342 page.body_html,
343 Some("<p>Welcome to our store!</p>".to_string())
344 );
345 assert_eq!(page.author, Some("Store Admin".to_string()));
346 assert!(page.template_suffix.is_none());
347 assert!(page.published_at.is_some());
348 assert!(page.created_at.is_some());
349 assert!(page.updated_at.is_some());
350 assert_eq!(
351 page.admin_graphql_api_id,
352 Some("gid://shopify/OnlineStorePage/131092082".to_string())
353 );
354 }
355
356 #[test]
357 fn test_page_list_params_serialization() {
358 let params = PageListParams {
359 title: Some("Contact".to_string()),
360 handle: Some("contact".to_string()),
361 published_status: Some("published".to_string()),
362 limit: Some(50),
363 since_id: Some(100),
364 ..Default::default()
365 };
366
367 let json = serde_json::to_value(¶ms).unwrap();
368
369 assert_eq!(json["title"], "Contact");
370 assert_eq!(json["handle"], "contact");
371 assert_eq!(json["published_status"], "published");
372 assert_eq!(json["limit"], 50);
373 assert_eq!(json["since_id"], 100);
374
375 assert!(json.get("created_at_min").is_none());
377 assert!(json.get("page_info").is_none());
378 }
379
380 #[test]
381 fn test_page_count_params_serialization() {
382 let params = PageCountParams {
383 title: Some("About".to_string()),
384 published_status: Some("any".to_string()),
385 ..Default::default()
386 };
387
388 let json = serde_json::to_value(¶ms).unwrap();
389
390 assert_eq!(json["title"], "About");
391 assert_eq!(json["published_status"], "any");
392
393 let empty_params = PageCountParams::default();
395 let empty_json = serde_json::to_value(&empty_params).unwrap();
396 assert_eq!(empty_json, serde_json::json!({}));
397 }
398
399 #[test]
400 fn test_page_path_constants_are_correct() {
401 let find_path = get_path(Page::PATHS, ResourceOperation::Find, &["id"]);
403 assert!(find_path.is_some());
404 assert_eq!(find_path.unwrap().template, "pages/{id}");
405 assert_eq!(find_path.unwrap().http_method, HttpMethod::Get);
406
407 let all_path = get_path(Page::PATHS, ResourceOperation::All, &[]);
409 assert!(all_path.is_some());
410 assert_eq!(all_path.unwrap().template, "pages");
411 assert_eq!(all_path.unwrap().http_method, HttpMethod::Get);
412
413 let count_path = get_path(Page::PATHS, ResourceOperation::Count, &[]);
415 assert!(count_path.is_some());
416 assert_eq!(count_path.unwrap().template, "pages/count");
417 assert_eq!(count_path.unwrap().http_method, HttpMethod::Get);
418
419 let create_path = get_path(Page::PATHS, ResourceOperation::Create, &[]);
421 assert!(create_path.is_some());
422 assert_eq!(create_path.unwrap().template, "pages");
423 assert_eq!(create_path.unwrap().http_method, HttpMethod::Post);
424
425 let update_path = get_path(Page::PATHS, ResourceOperation::Update, &["id"]);
427 assert!(update_path.is_some());
428 assert_eq!(update_path.unwrap().template, "pages/{id}");
429 assert_eq!(update_path.unwrap().http_method, HttpMethod::Put);
430
431 let delete_path = get_path(Page::PATHS, ResourceOperation::Delete, &["id"]);
433 assert!(delete_path.is_some());
434 assert_eq!(delete_path.unwrap().template, "pages/{id}");
435 assert_eq!(delete_path.unwrap().http_method, HttpMethod::Delete);
436
437 assert_eq!(Page::NAME, "Page");
439 assert_eq!(Page::PLURAL, "pages");
440 }
441
442 #[test]
443 fn test_page_get_id_returns_correct_value() {
444 let page_with_id = Page {
446 id: Some(123456789),
447 title: Some("Test Page".to_string()),
448 ..Default::default()
449 };
450 assert_eq!(page_with_id.get_id(), Some(123456789));
451
452 let page_without_id = Page {
454 id: None,
455 title: Some("New Page".to_string()),
456 ..Default::default()
457 };
458 assert_eq!(page_without_id.get_id(), None);
459 }
460
461 #[test]
462 fn test_page_published_at_for_scheduling() {
463 let future_date = DateTime::parse_from_rfc3339("2025-12-31T23:59:59Z")
465 .unwrap()
466 .with_timezone(&Utc);
467
468 let page = Page {
469 title: Some("Upcoming Sale".to_string()),
470 body_html: Some("<p>Coming soon!</p>".to_string()),
471 published_at: Some(future_date),
472 ..Default::default()
473 };
474
475 let json = serde_json::to_value(&page).unwrap();
476 assert!(json["published_at"].as_str().is_some());
477 assert!(json["published_at"]
478 .as_str()
479 .unwrap()
480 .contains("2025-12-31"));
481 }
482}