use crate::error::{Error, Result};
use chrono::{DateTime, Utc};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub enum ExchangeStatus {
Proposed,
InProgress,
Complete,
Cancelled,
}
impl Default for ExchangeStatus {
fn default() -> Self {
ExchangeStatus::Proposed
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct Agreement {
pub id: String,
pub name: String,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub note: Option<String>,
pub created_at: DateTime<Utc>,
}
impl Agreement {
pub fn new(id: impl Into<String>, name: impl Into<String>) -> Self {
Self {
id: id.into(),
name: name.into(),
note: None,
created_at: Utc::now(),
}
}
pub fn with_note(mut self, note: impl Into<String>) -> Self {
self.note = Some(note.into());
self
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct Exchange {
pub id: String,
pub name: String,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub realized_in: Option<String>,
#[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Vec::is_empty"))]
pub transfers: Vec<String>,
pub status: ExchangeStatus,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub in_scope_of: Option<String>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub note: Option<String>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
impl Exchange {
pub fn builder() -> ExchangeBuilder {
ExchangeBuilder::default()
}
pub fn add_transfer(&mut self, transfer_id: impl Into<String>) {
self.transfers.push(transfer_id.into());
self.updated_at = Utc::now();
}
pub fn start(&mut self) {
if self.status == ExchangeStatus::Proposed {
self.status = ExchangeStatus::InProgress;
self.updated_at = Utc::now();
}
}
pub fn complete(&mut self) {
if self.status == ExchangeStatus::InProgress {
self.status = ExchangeStatus::Complete;
self.updated_at = Utc::now();
}
}
pub fn cancel(&mut self) {
if self.status != ExchangeStatus::Complete {
self.status = ExchangeStatus::Cancelled;
self.updated_at = Utc::now();
}
}
pub fn is_complete(&self) -> bool {
matches!(self.status, ExchangeStatus::Complete)
}
}
#[derive(Debug, Default)]
pub struct ExchangeBuilder {
id: Option<String>,
name: Option<String>,
realized_in: Option<String>,
transfers: Vec<String>,
in_scope_of: Option<String>,
note: Option<String>,
}
impl ExchangeBuilder {
pub fn id(mut self, id: impl Into<String>) -> Self {
self.id = Some(id.into());
self
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn realized_in(mut self, agreement_id: impl Into<String>) -> Self {
self.realized_in = Some(agreement_id.into());
self
}
pub fn add_transfer(mut self, transfer_id: impl Into<String>) -> Self {
self.transfers.push(transfer_id.into());
self
}
pub fn in_scope_of(mut self, scope: impl Into<String>) -> Self {
self.in_scope_of = Some(scope.into());
self
}
pub fn note(mut self, note: impl Into<String>) -> Self {
self.note = Some(note.into());
self
}
pub fn build(self) -> Result<Exchange> {
let id = self.id.ok_or_else(|| Error::missing_field("id"))?;
let name = self.name.ok_or_else(|| Error::missing_field("name"))?;
let now = Utc::now();
Ok(Exchange {
id,
name,
realized_in: self.realized_in,
transfers: self.transfers,
status: ExchangeStatus::Proposed,
in_scope_of: self.in_scope_of,
note: self.note,
created_at: now,
updated_at: now,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_agreement_creation() {
let agreement = Agreement::new("agreement-001", "Sale Agreement")
.with_note("Agreement for tomato sale");
assert_eq!(agreement.id, "agreement-001");
assert_eq!(agreement.name, "Sale Agreement");
assert!(agreement.note.is_some());
}
#[test]
fn test_exchange_builder() {
let exchange = Exchange::builder()
.id("exchange-001")
.name("Tomato for Money")
.realized_in("agreement-001")
.build()
.unwrap();
assert_eq!(exchange.id, "exchange-001");
assert_eq!(exchange.status, ExchangeStatus::Proposed);
}
#[test]
fn test_exchange_lifecycle() {
let mut exchange = Exchange::builder()
.id("exchange-001")
.name("Test Exchange")
.build()
.unwrap();
assert_eq!(exchange.status, ExchangeStatus::Proposed);
exchange.start();
assert_eq!(exchange.status, ExchangeStatus::InProgress);
exchange.add_transfer("transfer-001");
exchange.add_transfer("transfer-002");
exchange.complete();
assert_eq!(exchange.status, ExchangeStatus::Complete);
assert!(exchange.is_complete());
}
#[cfg(feature = "serde")]
#[test]
fn test_exchange_serialization() {
let exchange = Exchange::builder()
.id("exchange-001")
.name("Test")
.add_transfer("transfer-001")
.build()
.unwrap();
let json = serde_json::to_string(&exchange).unwrap();
let parsed: Exchange = serde_json::from_str(&json).unwrap();
assert_eq!(exchange.id, parsed.id);
assert_eq!(exchange.transfers, parsed.transfers);
}
}