use crate::money::{Money, MoneyError};
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Address {
pub contact_name: Option<Box<str>>,
pub line: Box<str>,
pub city: Box<str>,
pub country: Box<str>,
pub zip_code: Option<Box<str>>,
}
impl Address {
#[must_use]
pub fn new(
line: impl Into<Box<str>>,
city: impl Into<Box<str>>,
country: impl Into<Box<str>>,
) -> Self {
Self {
contact_name: None,
line: line.into(),
city: city.into(),
country: country.into(),
zip_code: None,
}
}
#[must_use]
pub fn contact_name(mut self, name: impl Into<Box<str>>) -> Self {
self.contact_name = Some(name.into());
self
}
#[must_use]
pub fn zip_code(mut self, zip_code: impl Into<Box<str>>) -> Self {
self.zip_code = Some(zip_code.into());
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Buyer {
pub name: Box<str>,
pub surname: Option<Box<str>>,
pub email: Box<str>,
pub phone: Option<Box<str>>,
pub identity_number: Option<Box<str>>,
pub ip: Option<Box<str>>,
pub address: Option<Address>,
}
impl Buyer {
#[must_use]
pub fn new(name: impl Into<Box<str>>, email: impl Into<Box<str>>) -> Self {
Self {
name: name.into(),
surname: None,
email: email.into(),
phone: None,
identity_number: None,
ip: None,
address: None,
}
}
#[must_use]
pub fn surname(mut self, surname: impl Into<Box<str>>) -> Self {
self.surname = Some(surname.into());
self
}
#[must_use]
pub fn phone(mut self, phone: impl Into<Box<str>>) -> Self {
self.phone = Some(phone.into());
self
}
#[must_use]
pub fn identity_number(mut self, identity_number: impl Into<Box<str>>) -> Self {
self.identity_number = Some(identity_number.into());
self
}
#[must_use]
pub fn ip(mut self, ip: impl Into<Box<str>>) -> Self {
self.ip = Some(ip.into());
self
}
#[must_use]
pub fn address(mut self, address: Address) -> Self {
self.address = Some(address);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ItemKind {
#[default]
Physical,
Virtual,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct BasketItem {
pub id: Box<str>,
pub name: Box<str>,
pub category: Option<Box<str>>,
pub kind: ItemKind,
pub price: Money,
pub quantity: u32,
}
impl BasketItem {
#[must_use]
pub fn new(id: impl Into<Box<str>>, name: impl Into<Box<str>>, price: Money) -> Self {
Self {
id: id.into(),
name: name.into(),
category: None,
kind: ItemKind::Physical,
price,
quantity: 1,
}
}
#[must_use]
pub fn category(mut self, category: impl Into<Box<str>>) -> Self {
self.category = Some(category.into());
self
}
#[must_use]
pub const fn virtual_item(mut self) -> Self {
self.kind = ItemKind::Virtual;
self
}
#[must_use]
pub const fn quantity(mut self, quantity: u32) -> Self {
self.quantity = quantity;
self
}
pub fn line_total(&self) -> Result<Money, MoneyError> {
self.price.checked_mul(self.quantity)
}
}
#[cfg(test)]
mod tests {
use super::{Address, BasketItem, Buyer, ItemKind};
use crate::money::{Currency, Money};
#[test]
fn a_buyer_carries_only_what_every_provider_asks_for_until_told_more() {
let plain = Buyer::new("Ayse", "ayse@example.test");
assert!(plain.surname.is_none());
assert!(plain.identity_number.is_none());
let full = Buyer::new("Ayse", "ayse@example.test")
.surname("Yilmaz")
.identity_number("11111111111")
.ip("203.0.113.7")
.phone("+905350000000")
.address(Address::new("Bagdat Cad. 1", "Istanbul", "Turkey").zip_code("34000"));
assert_eq!(full.surname.as_deref(), Some("Yilmaz"));
assert_eq!(
full.address.expect("an address").zip_code.as_deref(),
Some("34000")
);
}
#[test]
fn a_line_ships_unless_it_is_told_not_to() {
let item = BasketItem::new(
"sku-1",
"Kahve",
Money::from_minor_units(14_990, Currency::Try),
);
assert_eq!(item.kind, ItemKind::Physical);
assert_eq!(item.quantity, 1);
let download =
BasketItem::new("sku-2", "PDF", Money::from_minor_units(1000, Currency::Try))
.virtual_item()
.quantity(3)
.category("Kitap");
assert_eq!(download.kind, ItemKind::Virtual);
assert_eq!(download.quantity, 3);
assert_eq!(download.category.as_deref(), Some("Kitap"));
}
#[test]
fn a_line_total_is_the_unit_price_times_the_count() {
let three = BasketItem::new(
"sku-1",
"Kahve",
Money::from_minor_units(1499, Currency::Try),
)
.quantity(3);
assert_eq!(
three.line_total().expect("no overflow"),
Money::from_minor_units(4497, Currency::Try)
);
}
#[test]
fn a_line_that_would_overflow_says_so_rather_than_wrapping() {
let absurd = BasketItem::new(
"sku-1",
"Ev",
Money::from_minor_units(i64::MAX, Currency::Try),
)
.quantity(2);
assert!(absurd.line_total().is_err());
}
}