use std::fmt;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Paginated<T> {
pub data: Vec<T>,
pub next_cursor: Option<String>,
pub total: Option<u64>,
}
impl<T> Default for Paginated<T> {
fn default() -> Self {
Self {
data: Vec::new(),
next_cursor: None,
total: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Budget {
pub amount: f64,
pub currency: String,
pub kind: BudgetKind,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum BudgetKind {
Daily,
Lifetime,
}
impl fmt::Display for BudgetKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Daily => write!(f, "daily"),
Self::Lifetime => write!(f, "lifetime"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DateRange {
pub start: DateTime<Utc>,
pub end: DateTime<Utc>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum HttpMethod {
Get,
Post,
Delete,
}
impl fmt::Display for HttpMethod {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Get => write!(f, "GET"),
Self::Post => write!(f, "POST"),
Self::Delete => write!(f, "DELETE"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderHealth {
pub provider: String,
pub healthy: bool,
pub latency_ms: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn paginated_default_is_empty() {
let page: Paginated<String> = Paginated::default();
assert!(page.data.is_empty());
assert!(page.next_cursor.is_none());
assert!(page.total.is_none());
}
#[test]
fn budget_kind_display() {
assert_eq!(BudgetKind::Daily.to_string(), "daily");
assert_eq!(BudgetKind::Lifetime.to_string(), "lifetime");
}
#[test]
#[allow(clippy::expect_used)]
fn budget_kind_serde_roundtrip() {
let json = serde_json::to_string(&BudgetKind::Daily).expect("serialize");
assert_eq!(json, r#""daily""#);
let back: BudgetKind = serde_json::from_str(&json).expect("deserialize");
assert_eq!(back, BudgetKind::Daily);
}
#[test]
fn http_method_display() {
assert_eq!(HttpMethod::Get.to_string(), "GET");
assert_eq!(HttpMethod::Post.to_string(), "POST");
assert_eq!(HttpMethod::Delete.to_string(), "DELETE");
}
#[test]
#[allow(clippy::expect_used)]
fn http_method_serde_roundtrip() {
let json = serde_json::to_string(&HttpMethod::Post).expect("serialize");
assert_eq!(json, r#""POST""#);
let back: HttpMethod = serde_json::from_str(&json).expect("deserialize");
assert_eq!(back, HttpMethod::Post);
}
#[test]
#[allow(clippy::expect_used)]
fn provider_health_serializes_without_none_message() {
let health = ProviderHealth {
provider: "meta".into(),
healthy: true,
latency_ms: 42,
message: None,
};
let json = serde_json::to_string(&health).expect("serialize");
assert!(!json.contains("message"));
}
}