use std::collections::HashMap;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::clients::RestClient;
use crate::rest::{
build_path, get_path, ResourceError, ResourceOperation, ResourcePath, ResourceResponse,
RestResource,
};
use crate::HttpMethod;
use super::common::ChargeCurrency;
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
pub struct UsageCharge {
#[serde(skip_serializing)]
pub id: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub recurring_application_charge_id: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub price: Option<String>,
#[serde(skip_serializing)]
pub currency: Option<ChargeCurrency>,
#[serde(skip_serializing)]
pub created_at: Option<DateTime<Utc>>,
#[serde(skip_serializing)]
pub updated_at: Option<DateTime<Utc>>,
}
impl UsageCharge {
pub async fn count_with_parent(
client: &RestClient,
recurring_application_charge_id: u64,
_params: Option<()>,
) -> Result<u64, ResourceError> {
let mut ids: HashMap<&str, String> = HashMap::new();
ids.insert(
"recurring_application_charge_id",
recurring_application_charge_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 response = client.get(&url, None).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)
}
pub async fn find_with_parent(
client: &RestClient,
recurring_application_charge_id: u64,
id: u64,
_params: Option<UsageChargeFindParams>,
) -> Result<ResourceResponse<Self>, ResourceError> {
let mut ids: HashMap<&str, String> = HashMap::new();
ids.insert(
"recurring_application_charge_id",
recurring_application_charge_id.to_string(),
);
ids.insert("id", id.to_string());
let available_ids: Vec<&str> = ids.keys().copied().collect();
let path = get_path(Self::PATHS, ResourceOperation::Find, &available_ids).ok_or(
ResourceError::PathResolutionFailed {
resource: Self::NAME,
operation: "find",
},
)?;
let url = build_path(path.template, &ids);
let response = client.get(&url, None).await?;
if !response.is_ok() {
return Err(ResourceError::from_http_response(
response.code,
&response.body,
Self::NAME,
Some(&id.to_string()),
response.request_id(),
));
}
let key = Self::resource_key();
ResourceResponse::from_http_response(response, &key)
}
}
impl RestResource for UsageCharge {
type Id = u64;
type FindParams = UsageChargeFindParams;
type AllParams = UsageChargeListParams;
type CountParams = ();
const NAME: &'static str = "UsageCharge";
const PLURAL: &'static str = "usage_charges";
const PATHS: &'static [ResourcePath] = &[
ResourcePath::new(
HttpMethod::Get,
ResourceOperation::Find,
&["recurring_application_charge_id", "id"],
"recurring_application_charges/{recurring_application_charge_id}/usage_charges/{id}",
),
ResourcePath::new(
HttpMethod::Get,
ResourceOperation::All,
&["recurring_application_charge_id"],
"recurring_application_charges/{recurring_application_charge_id}/usage_charges",
),
ResourcePath::new(
HttpMethod::Post,
ResourceOperation::Create,
&["recurring_application_charge_id"],
"recurring_application_charges/{recurring_application_charge_id}/usage_charges",
),
];
fn get_id(&self) -> Option<Self::Id> {
self.id
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct UsageChargeFindParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub fields: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct UsageChargeListParams {
#[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 fields: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rest::{get_path, ResourceOperation};
#[test]
fn test_usage_charge_serialization() {
let charge = UsageCharge {
id: Some(12345),
recurring_application_charge_id: Some(455696195),
description: Some("100 emails sent".to_string()),
price: Some("1.00".to_string()),
currency: Some(ChargeCurrency::new("USD")),
created_at: Some(
DateTime::parse_from_rfc3339("2024-01-15T10:30:00Z")
.unwrap()
.with_timezone(&Utc),
),
updated_at: Some(
DateTime::parse_from_rfc3339("2024-01-15T10:35:00Z")
.unwrap()
.with_timezone(&Utc),
),
};
let json = serde_json::to_string(&charge).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["recurring_application_charge_id"], 455696195);
assert_eq!(parsed["description"], "100 emails sent");
assert_eq!(parsed["price"], "1.00");
assert!(parsed.get("id").is_none());
assert!(parsed.get("currency").is_none());
assert!(parsed.get("created_at").is_none());
assert!(parsed.get("updated_at").is_none());
}
#[test]
fn test_usage_charge_deserialization() {
let json = r#"{
"id": 1034618207,
"recurring_application_charge_id": 455696195,
"description": "Super Mega Plan 1000 emails",
"price": "1.00",
"currency": {
"currency": "USD"
},
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:35:00Z"
}"#;
let charge: UsageCharge = serde_json::from_str(json).unwrap();
assert_eq!(charge.id, Some(1034618207));
assert_eq!(charge.recurring_application_charge_id, Some(455696195));
assert_eq!(
charge.description,
Some("Super Mega Plan 1000 emails".to_string())
);
assert_eq!(charge.price, Some("1.00".to_string()));
assert_eq!(charge.currency.as_ref().unwrap().code(), Some("USD"));
assert!(charge.created_at.is_some());
assert!(charge.updated_at.is_some());
}
#[test]
fn test_usage_charge_nested_paths() {
let find_path = get_path(
UsageCharge::PATHS,
ResourceOperation::Find,
&["recurring_application_charge_id", "id"],
);
assert!(find_path.is_some());
assert_eq!(
find_path.unwrap().template,
"recurring_application_charges/{recurring_application_charge_id}/usage_charges/{id}"
);
let find_without_parent = get_path(UsageCharge::PATHS, ResourceOperation::Find, &["id"]);
assert!(find_without_parent.is_none());
let all_path = get_path(
UsageCharge::PATHS,
ResourceOperation::All,
&["recurring_application_charge_id"],
);
assert!(all_path.is_some());
assert_eq!(
all_path.unwrap().template,
"recurring_application_charges/{recurring_application_charge_id}/usage_charges"
);
let all_without_parent = get_path(UsageCharge::PATHS, ResourceOperation::All, &[]);
assert!(all_without_parent.is_none());
let create_path = get_path(
UsageCharge::PATHS,
ResourceOperation::Create,
&["recurring_application_charge_id"],
);
assert!(create_path.is_some());
assert_eq!(
create_path.unwrap().template,
"recurring_application_charges/{recurring_application_charge_id}/usage_charges"
);
assert_eq!(create_path.unwrap().http_method, HttpMethod::Post);
let update_path = get_path(
UsageCharge::PATHS,
ResourceOperation::Update,
&["recurring_application_charge_id", "id"],
);
assert!(update_path.is_none());
let delete_path = get_path(
UsageCharge::PATHS,
ResourceOperation::Delete,
&["recurring_application_charge_id", "id"],
);
assert!(delete_path.is_none());
}
#[test]
fn test_usage_charge_list_params() {
let params = UsageChargeListParams {
limit: Some(50),
since_id: Some(100),
fields: Some("id,description,price".to_string()),
};
let json = serde_json::to_value(¶ms).unwrap();
assert_eq!(json["limit"], 50);
assert_eq!(json["since_id"], 100);
assert_eq!(json["fields"], "id,description,price");
let empty_params = UsageChargeListParams::default();
let empty_json = serde_json::to_value(&empty_params).unwrap();
assert_eq!(empty_json, serde_json::json!({}));
}
#[test]
fn test_usage_charge_constants() {
assert_eq!(UsageCharge::NAME, "UsageCharge");
assert_eq!(UsageCharge::PLURAL, "usage_charges");
}
#[test]
fn test_usage_charge_get_id() {
let charge_with_id = UsageCharge {
id: Some(12345),
..Default::default()
};
assert_eq!(charge_with_id.get_id(), Some(12345));
let charge_without_id = UsageCharge::default();
assert_eq!(charge_without_id.get_id(), None);
}
#[test]
fn test_usage_charge_with_currency_nested_object() {
let json = r#"{
"id": 123,
"recurring_application_charge_id": 456,
"description": "Test charge",
"price": "5.00",
"currency": {
"currency": "EUR"
}
}"#;
let charge: UsageCharge = serde_json::from_str(json).unwrap();
assert!(charge.currency.is_some());
let currency = charge.currency.unwrap();
assert_eq!(currency.code(), Some("EUR"));
}
}