use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Money {
pub amount: i64,
pub currency: Currency,
}
impl Money {
pub fn new(amount: i64, currency: Currency) -> Self {
Self { amount, currency }
}
pub fn usd(cents: i64) -> Self {
Self::new(cents, Currency::Usd)
}
pub fn to_major_unit(&self) -> f64 {
self.amount as f64 / 100.0
}
pub fn from_major_unit(amount: f64, currency: Currency) -> Self {
Self::new((amount * 100.0).round() as i64, currency)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub enum Currency {
#[serde(rename = "usd")]
Usd,
#[serde(rename = "eur")]
Eur,
#[serde(rename = "gbp")]
Gbp,
#[serde(rename = "cad")]
Cad,
#[serde(rename = "aud")]
Aud,
#[serde(rename = "jpy")]
Jpy,
}
impl Currency {
pub fn decimal_places(&self) -> u8 {
match self {
Currency::Jpy => 0, _ => 2,
}
}
pub fn symbol(&self) -> &'static str {
match self {
Currency::Usd => "$",
Currency::Eur => "€",
Currency::Gbp => "£",
Currency::Cad => "CA$",
Currency::Aud => "A$",
Currency::Jpy => "¥",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Timestamp(#[serde(with = "chrono::serde::ts_seconds")] pub DateTime<Utc>);
impl Timestamp {
pub fn now() -> Self {
Self(Utc::now())
}
pub fn from_datetime(dt: DateTime<Utc>) -> Self {
Self(dt)
}
pub fn datetime(&self) -> DateTime<Utc> {
self.0
}
pub fn as_secs(&self) -> i64 {
self.0.timestamp()
}
}
impl From<DateTime<Utc>> for Timestamp {
fn from(dt: DateTime<Utc>) -> Self {
Self(dt)
}
}
pub type Metadata = HashMap<String, serde_json::Value>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListResponse<T> {
pub data: Vec<T>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub has_more: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub total: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<String>,
}
impl<T> ListResponse<T> {
pub fn new(data: Vec<T>, has_more: bool) -> Self {
Self {
data,
has_more: Some(has_more),
total: None,
next_cursor: None,
}
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
pub fn len(&self) -> usize {
self.data.len()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub enum Interval {
Day,
Week,
Month,
Year,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum Status {
Active,
Inactive,
Pending,
Cancelled,
Expired,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_money_creation() {
let money = Money::new(1000, Currency::Usd);
assert_eq!(money.amount, 1000);
assert_eq!(money.currency, Currency::Usd);
}
#[test]
fn test_money_usd() {
let money = Money::usd(2500);
assert_eq!(money.amount, 2500);
assert_eq!(money.currency, Currency::Usd);
assert_eq!(money.to_major_unit(), 25.0);
}
#[test]
fn test_money_from_major_unit() {
let money = Money::from_major_unit(49.99, Currency::Usd);
assert_eq!(money.amount, 4999);
assert_eq!(money.currency, Currency::Usd);
}
#[test]
fn test_currency_serialization() {
let usd = Currency::Usd;
let json = serde_json::to_string(&usd).unwrap();
assert_eq!(json, r#""usd""#);
let deserialized: Currency = serde_json::from_str(&json).unwrap();
assert_eq!(usd, deserialized);
}
#[test]
fn test_currency_symbols() {
assert_eq!(Currency::Usd.symbol(), "$");
assert_eq!(Currency::Eur.symbol(), "€");
assert_eq!(Currency::Gbp.symbol(), "£");
}
#[test]
fn test_currency_decimal_places() {
assert_eq!(Currency::Usd.decimal_places(), 2);
assert_eq!(Currency::Jpy.decimal_places(), 0);
}
#[test]
fn test_timestamp() {
let now = Timestamp::now();
assert!(now.as_secs() > 0);
}
#[test]
fn test_timestamp_serialization() {
let dt = DateTime::parse_from_rfc3339("2024-01-01T00:00:00Z")
.unwrap()
.with_timezone(&Utc);
let ts = Timestamp::from_datetime(dt);
let json = serde_json::to_string(&ts).unwrap();
let deserialized: Timestamp = serde_json::from_str(&json).unwrap();
assert_eq!(ts, deserialized);
}
#[test]
fn test_list_response() {
let response = ListResponse::new(vec![1, 2, 3], true);
assert_eq!(response.len(), 3);
assert!(!response.is_empty());
assert_eq!(response.has_more, Some(true));
}
#[test]
fn test_interval_serialization() {
let interval = Interval::Month;
let json = serde_json::to_string(&interval).unwrap();
assert_eq!(json, r#""month""#);
}
}