use std::collections::HashMap;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::clients::RestClient;
use crate::rest::{
build_path, get_path, ReadOnlyResource, ResourceError, ResourceOperation, ResourcePath,
ResourceResponse, RestResource,
};
use crate::HttpMethod;
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
pub struct Event {
#[serde(skip_serializing)]
pub id: Option<u64>,
#[serde(skip_serializing)]
pub subject_id: Option<u64>,
#[serde(skip_serializing)]
pub subject_type: Option<String>,
#[serde(skip_serializing)]
pub verb: Option<String>,
#[serde(skip_serializing)]
pub arguments: Option<Vec<String>>,
#[serde(skip_serializing)]
pub message: Option<String>,
#[serde(skip_serializing)]
pub description: Option<String>,
#[serde(skip_serializing)]
pub body: Option<String>,
#[serde(skip_serializing)]
pub path: Option<String>,
#[serde(skip_serializing)]
pub created_at: Option<DateTime<Utc>>,
}
impl Event {
pub async fn all_for_owner<OwnerId: std::fmt::Display + Send>(
client: &RestClient,
owner_id_name: &str,
owner_id: OwnerId,
params: Option<EventListParams>,
) -> Result<ResourceResponse<Vec<Self>>, ResourceError> {
let mut ids: HashMap<&str, String> = HashMap::new();
ids.insert(owner_id_name, owner_id.to_string());
let available_ids: Vec<&str> = ids.keys().copied().collect();
let path = get_path(Self::PATHS, ResourceOperation::All, &available_ids).ok_or(
ResourceError::PathResolutionFailed {
resource: Self::NAME,
operation: "all",
},
)?;
let url = build_path(path.template, &ids);
let query = params
.map(|p| {
let value = serde_json::to_value(&p).map_err(|e| {
ResourceError::Http(crate::clients::HttpError::Response(
crate::clients::HttpResponseError {
code: 400,
message: format!("Failed to serialize params: {e}"),
error_reference: None,
},
))
})?;
let mut query = HashMap::new();
if let serde_json::Value::Object(map) = value {
for (key, val) in map {
match val {
serde_json::Value::String(s) => {
query.insert(key, s);
}
serde_json::Value::Number(n) => {
query.insert(key, n.to_string());
}
serde_json::Value::Bool(b) => {
query.insert(key, b.to_string());
}
_ => {}
}
}
}
Ok::<_, ResourceError>(query)
})
.transpose()?
.filter(|q| !q.is_empty());
let response = client.get(&url, query).await?;
if !response.is_ok() {
return Err(ResourceError::from_http_response(
response.code,
&response.body,
Self::NAME,
None,
response.request_id(),
));
}
let key = Self::PLURAL;
ResourceResponse::from_http_response(response, key)
}
pub async fn count_for_owner<OwnerId: std::fmt::Display + Send>(
client: &RestClient,
owner_id_name: &str,
owner_id: OwnerId,
params: Option<EventCountParams>,
) -> Result<u64, ResourceError> {
let mut ids: HashMap<&str, String> = HashMap::new();
ids.insert(owner_id_name, owner_id.to_string());
let available_ids: Vec<&str> = ids.keys().copied().collect();
let path = get_path(Self::PATHS, ResourceOperation::Count, &available_ids).ok_or(
ResourceError::PathResolutionFailed {
resource: Self::NAME,
operation: "count",
},
)?;
let url = build_path(path.template, &ids);
let query = params
.map(|p| {
let value = serde_json::to_value(&p).map_err(|e| {
ResourceError::Http(crate::clients::HttpError::Response(
crate::clients::HttpResponseError {
code: 400,
message: format!("Failed to serialize params: {e}"),
error_reference: None,
},
))
})?;
let mut query = HashMap::new();
if let serde_json::Value::Object(map) = value {
for (key, val) in map {
match val {
serde_json::Value::String(s) => {
query.insert(key, s);
}
serde_json::Value::Number(n) => {
query.insert(key, n.to_string());
}
serde_json::Value::Bool(b) => {
query.insert(key, b.to_string());
}
_ => {}
}
}
}
Ok::<_, ResourceError>(query)
})
.transpose()?
.filter(|q| !q.is_empty());
let response = client.get(&url, query).await?;
if !response.is_ok() {
return Err(ResourceError::from_http_response(
response.code,
&response.body,
Self::NAME,
None,
response.request_id(),
));
}
let count = response
.body
.get("count")
.and_then(serde_json::Value::as_u64)
.ok_or_else(|| {
ResourceError::Http(crate::clients::HttpError::Response(
crate::clients::HttpResponseError {
code: response.code,
message: "Missing 'count' in response".to_string(),
error_reference: response.request_id().map(ToString::to_string),
},
))
})?;
Ok(count)
}
}
impl RestResource for Event {
type Id = u64;
type FindParams = EventFindParams;
type AllParams = EventListParams;
type CountParams = EventCountParams;
const NAME: &'static str = "Event";
const PLURAL: &'static str = "events";
const PATHS: &'static [ResourcePath] = &[
ResourcePath::new(
HttpMethod::Get,
ResourceOperation::Find,
&["id"],
"events/{id}",
),
ResourcePath::new(HttpMethod::Get, ResourceOperation::All, &[], "events"),
ResourcePath::new(
HttpMethod::Get,
ResourceOperation::Count,
&[],
"events/count",
),
ResourcePath::new(
HttpMethod::Get,
ResourceOperation::All,
&["order_id"],
"orders/{order_id}/events",
),
ResourcePath::new(
HttpMethod::Get,
ResourceOperation::Count,
&["order_id"],
"orders/{order_id}/events/count",
),
ResourcePath::new(
HttpMethod::Get,
ResourceOperation::All,
&["product_id"],
"products/{product_id}/events",
),
ResourcePath::new(
HttpMethod::Get,
ResourceOperation::Count,
&["product_id"],
"products/{product_id}/events/count",
),
];
fn get_id(&self) -> Option<Self::Id> {
self.id
}
}
impl ReadOnlyResource for Event {}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct EventFindParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub fields: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct EventListParams {
#[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 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 filter: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub verb: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fields: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct EventCountParams {
#[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>>,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rest::{get_path, ReadOnlyResource, ResourceOperation, RestResource};
#[test]
fn test_event_implements_read_only_resource() {
fn assert_read_only<T: ReadOnlyResource>() {}
assert_read_only::<Event>();
}
#[test]
fn test_event_deserialization() {
let json = r#"{
"id": 677313116,
"subject_id": 921728736,
"subject_type": "Product",
"verb": "create",
"arguments": ["IPod Touch 8GB", "White"],
"message": "IPod Touch 8GB was created with colors White.",
"description": "Product created",
"body": null,
"path": "/admin/products/921728736",
"created_at": "2024-06-15T10:30:00Z"
}"#;
let event: Event = serde_json::from_str(json).unwrap();
assert_eq!(event.id, Some(677313116));
assert_eq!(event.subject_id, Some(921728736));
assert_eq!(event.subject_type, Some("Product".to_string()));
assert_eq!(event.verb, Some("create".to_string()));
assert!(event.arguments.is_some());
let args = event.arguments.unwrap();
assert_eq!(args.len(), 2);
assert_eq!(args[0], "IPod Touch 8GB");
assert_eq!(args[1], "White");
assert_eq!(
event.message,
Some("IPod Touch 8GB was created with colors White.".to_string())
);
assert_eq!(event.description, Some("Product created".to_string()));
assert!(event.body.is_none());
assert_eq!(
event.path,
Some("/admin/products/921728736".to_string())
);
assert!(event.created_at.is_some());
}
#[test]
fn test_event_read_only_paths() {
let find_path = get_path(Event::PATHS, ResourceOperation::Find, &["id"]);
assert!(find_path.is_some());
assert_eq!(find_path.unwrap().template, "events/{id}");
let all_path = get_path(Event::PATHS, ResourceOperation::All, &[]);
assert!(all_path.is_some());
assert_eq!(all_path.unwrap().template, "events");
let count_path = get_path(Event::PATHS, ResourceOperation::Count, &[]);
assert!(count_path.is_some());
assert_eq!(count_path.unwrap().template, "events/count");
let create_path = get_path(Event::PATHS, ResourceOperation::Create, &[]);
assert!(create_path.is_none());
let update_path = get_path(Event::PATHS, ResourceOperation::Update, &["id"]);
assert!(update_path.is_none());
let delete_path = get_path(Event::PATHS, ResourceOperation::Delete, &["id"]);
assert!(delete_path.is_none());
}
#[test]
fn test_event_polymorphic_owner_paths() {
let order_events = get_path(Event::PATHS, ResourceOperation::All, &["order_id"]);
assert!(order_events.is_some());
assert_eq!(
order_events.unwrap().template,
"orders/{order_id}/events"
);
let order_count = get_path(Event::PATHS, ResourceOperation::Count, &["order_id"]);
assert!(order_count.is_some());
assert_eq!(
order_count.unwrap().template,
"orders/{order_id}/events/count"
);
let product_events = get_path(Event::PATHS, ResourceOperation::All, &["product_id"]);
assert!(product_events.is_some());
assert_eq!(
product_events.unwrap().template,
"products/{product_id}/events"
);
let product_count = get_path(Event::PATHS, ResourceOperation::Count, &["product_id"]);
assert!(product_count.is_some());
assert_eq!(
product_count.unwrap().template,
"products/{product_id}/events/count"
);
}
#[test]
fn test_event_list_params() {
let params = EventListParams {
limit: Some(50),
filter: Some("Product".to_string()),
verb: Some("create".to_string()),
since_id: Some(100),
..Default::default()
};
let json = serde_json::to_value(¶ms).unwrap();
assert_eq!(json["limit"], 50);
assert_eq!(json["filter"], "Product");
assert_eq!(json["verb"], "create");
assert_eq!(json["since_id"], 100);
}
#[test]
fn test_event_constants() {
assert_eq!(Event::NAME, "Event");
assert_eq!(Event::PLURAL, "events");
}
#[test]
fn test_event_get_id() {
let event_with_id = Event {
id: Some(677313116),
verb: Some("create".to_string()),
..Default::default()
};
assert_eq!(event_with_id.get_id(), Some(677313116));
let event_without_id = Event::default();
assert_eq!(event_without_id.get_id(), None);
}
}