1use std::collections::HashMap;
53
54use chrono::{DateTime, Utc};
55use serde::{Deserialize, Serialize};
56
57use crate::clients::RestClient;
58use crate::rest::{
59 build_path, get_path, ReadOnlyResource, ResourceError, ResourceOperation, ResourcePath,
60 ResourceResponse, RestResource,
61};
62use crate::HttpMethod;
63
64#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
88pub struct Event {
89 #[serde(skip_serializing)]
91 pub id: Option<u64>,
92
93 #[serde(skip_serializing)]
95 pub subject_id: Option<u64>,
96
97 #[serde(skip_serializing)]
99 pub subject_type: Option<String>,
100
101 #[serde(skip_serializing)]
103 pub verb: Option<String>,
104
105 #[serde(skip_serializing)]
107 pub arguments: Option<Vec<String>>,
108
109 #[serde(skip_serializing)]
111 pub message: Option<String>,
112
113 #[serde(skip_serializing)]
115 pub description: Option<String>,
116
117 #[serde(skip_serializing)]
119 pub body: Option<String>,
120
121 #[serde(skip_serializing)]
123 pub path: Option<String>,
124
125 #[serde(skip_serializing)]
127 pub created_at: Option<DateTime<Utc>>,
128}
129
130impl Event {
131 pub async fn all_for_owner<OwnerId: std::fmt::Display + Send>(
152 client: &RestClient,
153 owner_id_name: &str,
154 owner_id: OwnerId,
155 params: Option<EventListParams>,
156 ) -> Result<ResourceResponse<Vec<Self>>, ResourceError> {
157 let mut ids: HashMap<&str, String> = HashMap::new();
158 ids.insert(owner_id_name, owner_id.to_string());
159
160 let available_ids: Vec<&str> = ids.keys().copied().collect();
161 let path = get_path(Self::PATHS, ResourceOperation::All, &available_ids).ok_or(
162 ResourceError::PathResolutionFailed {
163 resource: Self::NAME,
164 operation: "all",
165 },
166 )?;
167
168 let url = build_path(path.template, &ids);
169
170 let query = params
172 .map(|p| {
173 let value = serde_json::to_value(&p).map_err(|e| {
174 ResourceError::Http(crate::clients::HttpError::Response(
175 crate::clients::HttpResponseError {
176 code: 400,
177 message: format!("Failed to serialize params: {e}"),
178 error_reference: None,
179 },
180 ))
181 })?;
182
183 let mut query = HashMap::new();
184 if let serde_json::Value::Object(map) = value {
185 for (key, val) in map {
186 match val {
187 serde_json::Value::String(s) => {
188 query.insert(key, s);
189 }
190 serde_json::Value::Number(n) => {
191 query.insert(key, n.to_string());
192 }
193 serde_json::Value::Bool(b) => {
194 query.insert(key, b.to_string());
195 }
196 _ => {}
197 }
198 }
199 }
200 Ok::<_, ResourceError>(query)
201 })
202 .transpose()?
203 .filter(|q| !q.is_empty());
204
205 let response = client.get(&url, query).await?;
206
207 if !response.is_ok() {
208 return Err(ResourceError::from_http_response(
209 response.code,
210 &response.body,
211 Self::NAME,
212 None,
213 response.request_id(),
214 ));
215 }
216
217 let key = Self::PLURAL;
218 ResourceResponse::from_http_response(response, key)
219 }
220
221 pub async fn count_for_owner<OwnerId: std::fmt::Display + Send>(
230 client: &RestClient,
231 owner_id_name: &str,
232 owner_id: OwnerId,
233 params: Option<EventCountParams>,
234 ) -> Result<u64, ResourceError> {
235 let mut ids: HashMap<&str, String> = HashMap::new();
236 ids.insert(owner_id_name, owner_id.to_string());
237
238 let available_ids: Vec<&str> = ids.keys().copied().collect();
239 let path = get_path(Self::PATHS, ResourceOperation::Count, &available_ids).ok_or(
240 ResourceError::PathResolutionFailed {
241 resource: Self::NAME,
242 operation: "count",
243 },
244 )?;
245
246 let url = build_path(path.template, &ids);
247
248 let query = params
250 .map(|p| {
251 let value = serde_json::to_value(&p).map_err(|e| {
252 ResourceError::Http(crate::clients::HttpError::Response(
253 crate::clients::HttpResponseError {
254 code: 400,
255 message: format!("Failed to serialize params: {e}"),
256 error_reference: None,
257 },
258 ))
259 })?;
260
261 let mut query = HashMap::new();
262 if let serde_json::Value::Object(map) = value {
263 for (key, val) in map {
264 match val {
265 serde_json::Value::String(s) => {
266 query.insert(key, s);
267 }
268 serde_json::Value::Number(n) => {
269 query.insert(key, n.to_string());
270 }
271 serde_json::Value::Bool(b) => {
272 query.insert(key, b.to_string());
273 }
274 _ => {}
275 }
276 }
277 }
278 Ok::<_, ResourceError>(query)
279 })
280 .transpose()?
281 .filter(|q| !q.is_empty());
282
283 let response = client.get(&url, query).await?;
284
285 if !response.is_ok() {
286 return Err(ResourceError::from_http_response(
287 response.code,
288 &response.body,
289 Self::NAME,
290 None,
291 response.request_id(),
292 ));
293 }
294
295 let count = response
296 .body
297 .get("count")
298 .and_then(serde_json::Value::as_u64)
299 .ok_or_else(|| {
300 ResourceError::Http(crate::clients::HttpError::Response(
301 crate::clients::HttpResponseError {
302 code: response.code,
303 message: "Missing 'count' in response".to_string(),
304 error_reference: response.request_id().map(ToString::to_string),
305 },
306 ))
307 })?;
308
309 Ok(count)
310 }
311}
312
313impl RestResource for Event {
314 type Id = u64;
315 type FindParams = EventFindParams;
316 type AllParams = EventListParams;
317 type CountParams = EventCountParams;
318
319 const NAME: &'static str = "Event";
320 const PLURAL: &'static str = "events";
321
322 const PATHS: &'static [ResourcePath] = &[
327 ResourcePath::new(
329 HttpMethod::Get,
330 ResourceOperation::Find,
331 &["id"],
332 "events/{id}",
333 ),
334 ResourcePath::new(HttpMethod::Get, ResourceOperation::All, &[], "events"),
335 ResourcePath::new(
336 HttpMethod::Get,
337 ResourceOperation::Count,
338 &[],
339 "events/count",
340 ),
341 ResourcePath::new(
343 HttpMethod::Get,
344 ResourceOperation::All,
345 &["order_id"],
346 "orders/{order_id}/events",
347 ),
348 ResourcePath::new(
349 HttpMethod::Get,
350 ResourceOperation::Count,
351 &["order_id"],
352 "orders/{order_id}/events/count",
353 ),
354 ResourcePath::new(
356 HttpMethod::Get,
357 ResourceOperation::All,
358 &["product_id"],
359 "products/{product_id}/events",
360 ),
361 ResourcePath::new(
362 HttpMethod::Get,
363 ResourceOperation::Count,
364 &["product_id"],
365 "products/{product_id}/events/count",
366 ),
367 ];
368
369 fn get_id(&self) -> Option<Self::Id> {
370 self.id
371 }
372}
373
374impl ReadOnlyResource for Event {}
375
376#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
378pub struct EventFindParams {
379 #[serde(skip_serializing_if = "Option::is_none")]
381 pub fields: Option<String>,
382}
383
384#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
386pub struct EventListParams {
387 #[serde(skip_serializing_if = "Option::is_none")]
389 pub limit: Option<u32>,
390
391 #[serde(skip_serializing_if = "Option::is_none")]
393 pub since_id: Option<u64>,
394
395 #[serde(skip_serializing_if = "Option::is_none")]
397 pub created_at_min: Option<DateTime<Utc>>,
398
399 #[serde(skip_serializing_if = "Option::is_none")]
401 pub created_at_max: Option<DateTime<Utc>>,
402
403 #[serde(skip_serializing_if = "Option::is_none")]
405 pub filter: Option<String>,
406
407 #[serde(skip_serializing_if = "Option::is_none")]
409 pub verb: Option<String>,
410
411 #[serde(skip_serializing_if = "Option::is_none")]
413 pub fields: Option<String>,
414}
415
416#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
418pub struct EventCountParams {
419 #[serde(skip_serializing_if = "Option::is_none")]
421 pub created_at_min: Option<DateTime<Utc>>,
422
423 #[serde(skip_serializing_if = "Option::is_none")]
425 pub created_at_max: Option<DateTime<Utc>>,
426}
427
428#[cfg(test)]
429mod tests {
430 use super::*;
431 use crate::rest::{get_path, ReadOnlyResource, ResourceOperation, RestResource};
432
433 #[test]
434 fn test_event_implements_read_only_resource() {
435 fn assert_read_only<T: ReadOnlyResource>() {}
437 assert_read_only::<Event>();
438 }
439
440 #[test]
441 fn test_event_deserialization() {
442 let json = r#"{
443 "id": 677313116,
444 "subject_id": 921728736,
445 "subject_type": "Product",
446 "verb": "create",
447 "arguments": ["IPod Touch 8GB", "White"],
448 "message": "IPod Touch 8GB was created with colors White.",
449 "description": "Product created",
450 "body": null,
451 "path": "/admin/products/921728736",
452 "created_at": "2024-06-15T10:30:00Z"
453 }"#;
454
455 let event: Event = serde_json::from_str(json).unwrap();
456
457 assert_eq!(event.id, Some(677313116));
458 assert_eq!(event.subject_id, Some(921728736));
459 assert_eq!(event.subject_type, Some("Product".to_string()));
460 assert_eq!(event.verb, Some("create".to_string()));
461 assert!(event.arguments.is_some());
462 let args = event.arguments.unwrap();
463 assert_eq!(args.len(), 2);
464 assert_eq!(args[0], "IPod Touch 8GB");
465 assert_eq!(args[1], "White");
466 assert_eq!(
467 event.message,
468 Some("IPod Touch 8GB was created with colors White.".to_string())
469 );
470 assert_eq!(event.description, Some("Product created".to_string()));
471 assert!(event.body.is_none());
472 assert_eq!(
473 event.path,
474 Some("/admin/products/921728736".to_string())
475 );
476 assert!(event.created_at.is_some());
477 }
478
479 #[test]
480 fn test_event_read_only_paths() {
481 let find_path = get_path(Event::PATHS, ResourceOperation::Find, &["id"]);
483 assert!(find_path.is_some());
484 assert_eq!(find_path.unwrap().template, "events/{id}");
485
486 let all_path = get_path(Event::PATHS, ResourceOperation::All, &[]);
487 assert!(all_path.is_some());
488 assert_eq!(all_path.unwrap().template, "events");
489
490 let count_path = get_path(Event::PATHS, ResourceOperation::Count, &[]);
491 assert!(count_path.is_some());
492 assert_eq!(count_path.unwrap().template, "events/count");
493
494 let create_path = get_path(Event::PATHS, ResourceOperation::Create, &[]);
496 assert!(create_path.is_none());
497
498 let update_path = get_path(Event::PATHS, ResourceOperation::Update, &["id"]);
499 assert!(update_path.is_none());
500
501 let delete_path = get_path(Event::PATHS, ResourceOperation::Delete, &["id"]);
502 assert!(delete_path.is_none());
503 }
504
505 #[test]
506 fn test_event_polymorphic_owner_paths() {
507 let order_events = get_path(Event::PATHS, ResourceOperation::All, &["order_id"]);
509 assert!(order_events.is_some());
510 assert_eq!(
511 order_events.unwrap().template,
512 "orders/{order_id}/events"
513 );
514
515 let order_count = get_path(Event::PATHS, ResourceOperation::Count, &["order_id"]);
517 assert!(order_count.is_some());
518 assert_eq!(
519 order_count.unwrap().template,
520 "orders/{order_id}/events/count"
521 );
522
523 let product_events = get_path(Event::PATHS, ResourceOperation::All, &["product_id"]);
525 assert!(product_events.is_some());
526 assert_eq!(
527 product_events.unwrap().template,
528 "products/{product_id}/events"
529 );
530
531 let product_count = get_path(Event::PATHS, ResourceOperation::Count, &["product_id"]);
533 assert!(product_count.is_some());
534 assert_eq!(
535 product_count.unwrap().template,
536 "products/{product_id}/events/count"
537 );
538 }
539
540 #[test]
541 fn test_event_list_params() {
542 let params = EventListParams {
543 limit: Some(50),
544 filter: Some("Product".to_string()),
545 verb: Some("create".to_string()),
546 since_id: Some(100),
547 ..Default::default()
548 };
549
550 let json = serde_json::to_value(¶ms).unwrap();
551
552 assert_eq!(json["limit"], 50);
553 assert_eq!(json["filter"], "Product");
554 assert_eq!(json["verb"], "create");
555 assert_eq!(json["since_id"], 100);
556 }
557
558 #[test]
559 fn test_event_constants() {
560 assert_eq!(Event::NAME, "Event");
561 assert_eq!(Event::PLURAL, "events");
562 }
563
564 #[test]
565 fn test_event_get_id() {
566 let event_with_id = Event {
567 id: Some(677313116),
568 verb: Some("create".to_string()),
569 ..Default::default()
570 };
571 assert_eq!(event_with_id.get_id(), Some(677313116));
572
573 let event_without_id = Event::default();
574 assert_eq!(event_without_id.get_id(), None);
575 }
576}