use crate::types::CategoryId;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Classification {
Income,
Expense,
}
impl fmt::Display for Classification {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Income => write!(f, "income"),
Self::Expense => write!(f, "expense"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseClassificationError(String);
impl fmt::Display for ParseClassificationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Invalid classification: {}", self.0)
}
}
impl std::error::Error for ParseClassificationError {}
impl std::str::FromStr for Classification {
type Err = ParseClassificationError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"income" => Ok(Self::Income),
"expense" => Ok(Self::Expense),
_ => Err(ParseClassificationError(s.to_string())),
}
}
}
impl TryFrom<&str> for Classification {
type Error = ParseClassificationError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
value.parse()
}
}
impl TryFrom<String> for Classification {
type Error = ParseClassificationError;
fn try_from(value: String) -> Result<Self, Self::Error> {
value.parse()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
pub struct Category {
pub id: CategoryId,
pub name: String,
pub color: String,
pub icon: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
pub struct CategoryParent {
pub id: CategoryId,
pub name: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
pub struct CategoryDetail {
pub id: CategoryId,
pub name: String,
pub color: String,
pub icon: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent: Option<CategoryParent>,
pub subcategories_count: u32,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
pub struct CategoryCollection {
pub categories: Vec<CategoryDetail>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
pub(crate) struct CreateCategoryRequest {
pub category: CreateCategoryData,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
pub(crate) struct CreateCategoryData {
pub name: String,
pub color: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub lucide_icon: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent_id: Option<CategoryId>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
pub(crate) struct UpdateCategoryRequest {
pub category: UpdateCategoryData,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
pub(crate) struct UpdateCategoryData {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub color: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub lucide_icon: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent_id: Option<CategoryId>,
}